From 0f4df13126747e66687074e7d5413be7a6f9332f Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 11 Aug 2025 15:35:06 +0100 Subject: [PATCH 01/51] supplycommit: check asset metadata before starting state machine Before starting a new supply commit state machine, verify asset metadata to ensure the supply commit feature is properly supported. --- universe/supplycommit/env.go | 34 +++++++++++++++ universe/supplycommit/manager.go | 75 ++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index aa85b8b16..59ec933cf 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -123,6 +123,40 @@ type AssetLookup interface { rawKey *btcec.PublicKey) (keychain.KeyLocator, error) } +// fetchLatestAssetMetadata returns the latest asset metadata for the +// given asset specifier. +func fetchLatestAssetMetadata(ctx context.Context, lookup AssetLookup, + assetSpec asset.Specifier) (proof.MetaReveal, error) { + + var zero proof.MetaReveal + + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return zero, err + } + + // TODO(ffranr): This currently retrieves asset metadata using the + // genesis ID. Update it to retrieve by the latest asset ID instead, + // which will provide access to the most up-to-date canonical universe + // list. + assetGroup, err := lookup.QueryAssetGroupByGroupKey(ctx, groupKey) + if err != nil { + return zero, fmt.Errorf("unable to fetch asset group "+ + "by group key: %w", err) + } + + // Retrieve the asset metadata for the asset group. This will + // include the delegation key and universe commitment flag. + metaReveal, err := lookup.FetchAssetMetaForAsset( + ctx, assetGroup.Genesis.ID(), + ) + if err != nil { + return zero, fmt.Errorf("faild to fetch asset meta: %w", err) + } + + return *metaReveal, nil +} + // SupplyTreeView is an interface that allows the state machine to obtain an up // to date snapshot of the root supply tree, as the sub trees (ignore, burn, // mint) committed in the main supply tree. diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index adaf13f45..b73d0d289 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -2,15 +2,18 @@ package supplycommit import ( "context" + "errors" "fmt" "sync" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" + "github.com/lightninglabs/taproot-assets/address" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/mssmt" + "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightningnetwork/lnd/msgmux" @@ -135,6 +138,55 @@ func (m *Manager) Stop() error { return nil } +// ensureSupplyCommitSupport verifies that the asset group for the given +// asset specifier supports supply commitments, and that this node can generate +// supply commitments for it. +func (m *Manager) ensureSupplyCommitSupport(ctx context.Context, + metaReveal proof.MetaReveal) error { + + // If the universe commitment flag is not set on the asset metadata, + // then the asset group does not support supply commitments. + if !metaReveal.UniverseCommitments { + return fmt.Errorf("asset group metadata universe " + + "commitments flag indicates that asset does not " + + "support supply commitments") + } + + // If a delegation key is not present, then the asset group does not + // support supply commitments. + if metaReveal.DelegationKey.IsNone() { + return fmt.Errorf("asset group metadata does not " + + "specify delegation key, which is required for " + + "supply commitments") + } + + // Extract supply commitment delegation pub key from the asset metadata. + delegationPubKey, err := metaReveal.DelegationKey.UnwrapOrErr( + fmt.Errorf("delegation key not found for given asset"), + ) + if err != nil { + return err + } + + // Fetch the delegation key locator. We need to ensure that the + // delegation key is owned by this node, so that we can generate + // supply commitments (ignore tuples) for this asset group. + _, err = m.cfg.AssetLookup.FetchInternalKeyLocator( + ctx, &delegationPubKey, + ) + switch { + case errors.Is(err, address.ErrInternalKeyNotFound): + return fmt.Errorf("delegation key locator not found; " + + "only delegation key owners can ignore asset " + + "outpoints for this asset group") + case err != nil: + return fmt.Errorf("failed to fetch delegation key locator: %w", + err) + } + + return nil +} + // fetchStateMachine retrieves a state machine from the cache or creates a // new one if it doesn't exist. If a new state machine is created, it is also // started. @@ -155,6 +207,26 @@ func (m *Manager) fetchStateMachine( } // If the state machine is not found, create a new one. + // + // Before we can create a state machine, we need to ensure that the + // asset group supports supply commitments. If it doesn't, then we + // return an error. + ctx, cancel := m.WithCtxQuitNoTimeout() + defer cancel() + + metaReveal, err := fetchLatestAssetMetadata( + ctx, m.cfg.AssetLookup, assetSpec, + ) + if err != nil { + return nil, fmt.Errorf("faild to fetch asset meta: %w", err) + } + + err = m.ensureSupplyCommitSupport(ctx, metaReveal) + if err != nil { + return nil, fmt.Errorf("failed to ensure supply commit "+ + "support for asset: %w", err) + } + env := &Environment{ AssetSpec: assetSpec, TreeView: m.cfg.TreeView, @@ -170,9 +242,6 @@ func (m *Manager) fetchStateMachine( // Before we start the state machine, we'll need to fetch the current // state from disk, to see if we need to emit any new events. - ctx, cancel := m.WithCtxQuitNoTimeout() - defer cancel() - initialState, _, err := m.cfg.StateLog.FetchState(ctx, assetSpec) if err != nil { return nil, fmt.Errorf("unable to fetch current state: %w", err) From 01d0dbfd9af8449caa19890c878302b8c49db29c Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 22 Aug 2025 23:49:02 +0100 Subject: [PATCH 02/51] supplyverifier: add SupplySyncer for syncing supply commit leaves Introduce SupplySyncer to handle syncing of supply commit leaves from remote universe servers. --- log.go | 5 + universe/supplyverifier/log.go | 26 ++++ universe/supplyverifier/syncer.go | 224 ++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 universe/supplyverifier/log.go create mode 100644 universe/supplyverifier/syncer.go diff --git a/log.go b/log.go index 8a8ced315..06949d40b 100644 --- a/log.go +++ b/log.go @@ -16,6 +16,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapsend" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/signal" ) @@ -115,6 +116,10 @@ func SetupLoggers(root *build.SubLoggerManager, root, supplycommit.Subsystem, interceptor, supplycommit.UseLogger, ) + AddSubLogger( + root, supplycommit.Subsystem, interceptor, + supplyverifier.UseLogger, + ) AddSubLogger( root, commitment.Subsystem, interceptor, commitment.UseLogger, ) diff --git a/universe/supplyverifier/log.go b/universe/supplyverifier/log.go new file mode 100644 index 000000000..481702912 --- /dev/null +++ b/universe/supplyverifier/log.go @@ -0,0 +1,26 @@ +package supplyverifier + +import ( + "github.com/btcsuite/btclog/v2" +) + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "SUPV" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log = btclog.Disabled + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/universe/supplyverifier/syncer.go b/universe/supplyverifier/syncer.go new file mode 100644 index 000000000..1a24ea591 --- /dev/null +++ b/universe/supplyverifier/syncer.go @@ -0,0 +1,224 @@ +package supplyverifier + +import ( + "context" + "fmt" + "net/url" + + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/universe" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" +) + +// UniverseClient is an interface that represents a client connection to a +// remote universe server. +type UniverseClient interface { + // InsertSupplyCommit inserts a supply commitment for a specific + // asset group into the remote universe server. + InsertSupplyCommit(ctx context.Context, assetSpec asset.Specifier, + commitment supplycommit.RootCommitment, + updateLeaves supplycommit.SupplyLeaves, + chainProof supplycommit.ChainProof) error + + // Close closes the fetcher and cleans up any resources. + Close() error +} + +// UniverseClientFactory is a function type that creates UniverseClient +// instances for a given universe server address. +type UniverseClientFactory func(serverAddr universe.ServerAddr) (UniverseClient, + error) + +// SupplySyncerStore is an interface for storing synced leaves and state. +type SupplySyncerStore interface { + // LogSupplyCommitPush logs that a supply commitment and its leaves + // have been successfully pushed to a remote universe server. + LogSupplyCommitPush(ctx context.Context, serverAddr universe.ServerAddr, + assetSpec asset.Specifier, + commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error +} + +// UniverseFederationView is an interface that provides a view of the +// federation of universe servers. +type UniverseFederationView interface { + // UniverseServers returns a list of all known universe servers in + // the federation. + UniverseServers(ctx context.Context) ([]universe.ServerAddr, error) +} + +// SupplySyncerConfig is a configuration struct for creating a new +// SupplySyncer instance. +type SupplySyncerConfig struct { + // ClientFactory is a factory function that creates UniverseClient + // instances for specific universe server addresses. + ClientFactory UniverseClientFactory + + // Store is used to persist supply leaves to the local database. + Store SupplySyncerStore + + // UniverseFederationView is used to fetch the list of known + // universe servers in the federation. + UniverseFederationView UniverseFederationView +} + +// SupplySyncer is a struct that is responsible for retrieving supply leaves +// from a universe. +type SupplySyncer struct { + // cfg is the configuration for the SupplySyncer. + cfg SupplySyncerConfig +} + +// NewSupplySyncer creates a new SupplySyncer with a factory function for +// creating UniverseClient instances and a store for persisting leaves. +func NewSupplySyncer(cfg SupplySyncerConfig) SupplySyncer { + return SupplySyncer{ + cfg: cfg, + } +} + +// pushUniServer pushes the supply commitment to a specific universe server. +func (s *SupplySyncer) pushUniServer(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + updateLeaves supplycommit.SupplyLeaves, + chainProof supplycommit.ChainProof, + serverAddr universe.ServerAddr) error { + + // Create a client for the specific universe server address. + client, err := s.cfg.ClientFactory(serverAddr) + if err != nil { + return fmt.Errorf("unable to create universe client: %w", err) + } + + // Ensure the client is properly closed when we're done. + defer func() { + if closeErr := client.Close(); closeErr != nil { + log.Errorf("unable to close universe client: %v", + closeErr) + } + }() + + err = client.InsertSupplyCommit( + ctx, assetSpec, commitment, updateLeaves, chainProof, + ) + if err != nil { + return fmt.Errorf("unable to insert supply leaves: %w", err) + } + + // Log the successful insertion to the remote universe. + err = s.cfg.Store.LogSupplyCommitPush( + ctx, serverAddr, assetSpec, commitment, updateLeaves, + ) + if err != nil { + return fmt.Errorf("unable to log supply commit push: %w", err) + } + + return nil +} + +// fetchServerAddrs retrieves the list of universe server addresses that +// the syncer uses to interact with remote servers. +func (s *SupplySyncer) fetchServerAddrs(ctx context.Context, + canonicalUniverses []url.URL) ([]universe.ServerAddr, error) { + + var zero []universe.ServerAddr + + // Fetch latest set of universe federation server addresses. + fedAddrs, err := s.cfg.UniverseFederationView.UniverseServers(ctx) + if err != nil { + return zero, fmt.Errorf("unable to fetch universe servers: %w", + err) + } + + // Formulate final unique list of universe server addresses to push to. + uniqueAddrs := make(map[string]universe.ServerAddr) + for idx := range canonicalUniverses { + addrUrl := canonicalUniverses[idx] + serverAddr := universe.NewServerAddrFromStr(addrUrl.String()) + uniqueAddrs[serverAddr.HostStr()] = serverAddr + } + + for idx := range fedAddrs { + serverAddr := fedAddrs[idx] + uniqueAddrs[serverAddr.HostStr()] = serverAddr + } + + targetAddrs := make([]universe.ServerAddr, 0, len(uniqueAddrs)) + for _, serverAddr := range uniqueAddrs { + targetAddrs = append(targetAddrs, serverAddr) + } + + return targetAddrs, nil +} + +// PushSupplyCommitment pushes a supply commitment to the remote universe +// server. This function should block until the sync insertion is complete. +// +// Returns a map of per-server errors keyed by server host string and +// an internal error. If all pushes succeed, both return values are nil. +// If some pushes fail, the map contains only the failed servers and +// their corresponding errors. If there's an internal/system error that +// prevents the operation from proceeding, it's returned as the second +// value. +// +// NOTE: This function must be thread safe. +func (s *SupplySyncer) PushSupplyCommitment(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + updateLeaves supplycommit.SupplyLeaves, + chainProof supplycommit.ChainProof, + canonicalUniverses []url.URL) (map[string]error, error) { + + targetAddrs, err := s.fetchServerAddrs(ctx, canonicalUniverses) + if err != nil { + // This is an internal error that prevents the operation from + // proceeding. + return nil, fmt.Errorf("unable to fetch target universe "+ + "server addresses: %w", err) + } + + // Push the supply commitment to all target universe servers in + // parallel. Any error for a specific server will be captured in the + // pushErrs map and will not abort the entire operation. + pushErrs, err := fn.ParSliceErrCollect( + ctx, targetAddrs, func(ctx context.Context, + serverAddr universe.ServerAddr) error { + + // Push the supply commitment to the universe server. + err := s.pushUniServer( + ctx, assetSpec, commitment, updateLeaves, + chainProof, serverAddr, + ) + if err != nil { + return fmt.Errorf("unable to push supply "+ + "commitment (server_addr_id=%d, "+ + "server_addr_host_str=%s): %w", + serverAddr.ID, serverAddr.HostStr(), + err) + } + + return nil + }, + ) + if err != nil { + // This should not happen with ParSliceErrCollect, but handle it + // as an internal error. + return nil, fmt.Errorf("unable to push supply commitment: %w", + err) + } + + // Build a map of errors encountered while pushing to each server. + // If there were no errors, return nil for both values. + if len(pushErrs) == 0 { + return nil, nil + } + + errorMap := make(map[string]error) + for idx, fetchErr := range pushErrs { + serverAddr := targetAddrs[idx] + hostStr := serverAddr.HostStr() + errorMap[hostStr] = fetchErr + } + + return errorMap, nil +} From 4bd7517168ba3699047a76673c427e59f49f41f1 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 6 Aug 2025 19:25:51 +0100 Subject: [PATCH 03/51] tapdb: add supply syncer push log table Add persistent logging for supply syncer push events. Useful for debugging/troubleshooting. --- tapdb/migrations.go | 2 +- .../000045_supply_syncer_push_log.down.sql | 11 ++++ .../000045_supply_syncer_push_log.up.sql | 55 +++++++++++++++++++ tapdb/sqlc/models.go | 34 ++++++++---- tapdb/sqlc/schemas/generated_schema.sql | 42 +++++++++++++- tapdb/sqlc/supply_commit.sql.go | 3 +- 6 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 tapdb/sqlc/migrations/000045_supply_syncer_push_log.down.sql create mode 100644 tapdb/sqlc/migrations/000045_supply_syncer_push_log.up.sql diff --git a/tapdb/migrations.go b/tapdb/migrations.go index 74fcc4187..4666eb5c5 100644 --- a/tapdb/migrations.go +++ b/tapdb/migrations.go @@ -24,7 +24,7 @@ const ( // daemon. // // NOTE: This MUST be updated when a new migration is added. - LatestMigrationVersion = 44 + LatestMigrationVersion = 45 ) // DatabaseBackend is an interface that contains all methods our different diff --git a/tapdb/sqlc/migrations/000045_supply_syncer_push_log.down.sql b/tapdb/sqlc/migrations/000045_supply_syncer_push_log.down.sql new file mode 100644 index 000000000..d5c9c4867 --- /dev/null +++ b/tapdb/sqlc/migrations/000045_supply_syncer_push_log.down.sql @@ -0,0 +1,11 @@ +-- Drop the supply_syncer_push_log table and its indexes. +DROP INDEX IF EXISTS supply_syncer_push_log_server_address_idx; +DROP INDEX IF EXISTS supply_syncer_push_log_group_key_idx; +DROP TABLE IF EXISTS supply_syncer_push_log; + +-- Drop supply_commitments changes. +DROP INDEX IF EXISTS supply_commitments_outpoint_uk; +DROP INDEX IF EXISTS supply_commitments_spent_commitment_idx; + +ALTER TABLE supply_commitments + DROP COLUMN spent_commitment; diff --git a/tapdb/sqlc/migrations/000045_supply_syncer_push_log.up.sql b/tapdb/sqlc/migrations/000045_supply_syncer_push_log.up.sql new file mode 100644 index 000000000..127935a45 --- /dev/null +++ b/tapdb/sqlc/migrations/000045_supply_syncer_push_log.up.sql @@ -0,0 +1,55 @@ +-- Table to track supply commitment pushes to remote universe servers. +CREATE TABLE supply_syncer_push_log ( + id INTEGER PRIMARY KEY, + + -- The tweaked group key identifying the asset group this push log belongs + -- to. This should match the group_key format used in universe_supply_roots. + group_key BLOB NOT NULL CHECK(length(group_key) = 33), + + -- The highest block height among all supply leaves in this push. + max_pushed_block_height INTEGER NOT NULL, + + -- The server address (host:port) where the commitment was pushed. + server_address TEXT NOT NULL, + + -- The transaction ID (hash) of the supply commitment. + commit_txid BLOB NOT NULL CHECK(length(commit_txid) = 32), + + -- The supply commitment output index within the commitment transaction. + output_index INTEGER NOT NULL, + + -- The number of leaves included in this specific push (diff count between + -- last commitment and current commitment). + num_leaves_pushed INTEGER NOT NULL, + + -- The timestamp when this push log entry was created (unix timestamp in seconds). + created_at BIGINT NOT NULL +); + +-- Add index for frequent lookups by group key. +CREATE INDEX supply_syncer_push_log_group_key_idx + ON supply_syncer_push_log(group_key); + +-- Add index for lookups by server address. +CREATE INDEX supply_syncer_push_log_server_address_idx + ON supply_syncer_push_log(server_address); + +-- A nullable column to track the previous supply commitment that was spent to +-- create a new supply commitment. This is only NULL for the very first +-- commitment of an asset group, each subsequent commitment needs to spend a +-- prior commitment to ensure continuity in the supply chain. +ALTER TABLE supply_commitments + ADD COLUMN spent_commitment BIGINT + REFERENCES supply_commitments(commit_id); + +-- Add an index to speed up lookups by spent commitment. +CREATE INDEX supply_commitments_spent_commitment_idx + ON supply_commitments(spent_commitment); + +-- The outpoint of a supply commitment must be unique. Because we don't have a +-- separate field for the outpoint, we create a unique index over the chain +-- transaction ID and output index. This ensures that each commitment can be +-- uniquely identified by its transaction and output index, preventing +-- duplicate commitments for the same output. +CREATE UNIQUE INDEX supply_commitments_outpoint_uk + ON supply_commitments(chain_txn_id, output_index); diff --git a/tapdb/sqlc/models.go b/tapdb/sqlc/models.go index 97d4f3c89..fb1e3aeb2 100644 --- a/tapdb/sqlc/models.go +++ b/tapdb/sqlc/models.go @@ -401,17 +401,29 @@ type SupplyCommitUpdateType struct { } type SupplyCommitment struct { - CommitID int64 - GroupKey []byte - ChainTxnID int64 - OutputIndex sql.NullInt32 - InternalKeyID int64 - OutputKey []byte - BlockHeader []byte - BlockHeight sql.NullInt32 - MerkleProof []byte - SupplyRootHash []byte - SupplyRootSum sql.NullInt64 + CommitID int64 + GroupKey []byte + ChainTxnID int64 + OutputIndex sql.NullInt32 + InternalKeyID int64 + OutputKey []byte + BlockHeader []byte + BlockHeight sql.NullInt32 + MerkleProof []byte + SupplyRootHash []byte + SupplyRootSum sql.NullInt64 + SpentCommitment sql.NullInt64 +} + +type SupplySyncerPushLog struct { + ID int64 + GroupKey []byte + MaxPushedBlockHeight int32 + ServerAddress string + CommitTxid []byte + OutputIndex int32 + NumLeavesPushed int32 + CreatedAt int64 } type SupplyUpdateEvent struct { diff --git a/tapdb/sqlc/schemas/generated_schema.sql b/tapdb/sqlc/schemas/generated_schema.sql index d61be7eb6..0d686d16e 100644 --- a/tapdb/sqlc/schemas/generated_schema.sql +++ b/tapdb/sqlc/schemas/generated_schema.sql @@ -879,12 +879,52 @@ CREATE TABLE supply_commitments ( -- The root sum of the supply commitment at this snapshot. supply_root_sum BIGINT -); +, spent_commitment BIGINT + REFERENCES supply_commitments(commit_id)); CREATE INDEX supply_commitments_chain_txn_id_idx ON supply_commitments(chain_txn_id); CREATE INDEX supply_commitments_group_key_idx ON supply_commitments(group_key); +CREATE UNIQUE INDEX supply_commitments_outpoint_uk + ON supply_commitments(chain_txn_id, output_index); + +CREATE INDEX supply_commitments_spent_commitment_idx + ON supply_commitments(spent_commitment); + +CREATE TABLE supply_syncer_push_log ( + id INTEGER PRIMARY KEY, + + -- The tweaked group key identifying the asset group this push log belongs + -- to. This should match the group_key format used in universe_supply_roots. + group_key BLOB NOT NULL CHECK(length(group_key) = 33), + + -- The highest block height among all supply leaves in this push. + max_pushed_block_height INTEGER NOT NULL, + + -- The server address (host:port) where the commitment was pushed. + server_address TEXT NOT NULL, + + -- The transaction ID (hash) of the supply commitment. + commit_txid BLOB NOT NULL CHECK(length(commit_txid) = 32), + + -- The supply commitment output index within the commitment transaction. + output_index INTEGER NOT NULL, + + -- The number of leaves included in this specific push (diff count between + -- last commitment and current commitment). + num_leaves_pushed INTEGER NOT NULL, + + -- The timestamp when this push log entry was created (unix timestamp in seconds). + created_at BIGINT NOT NULL +); + +CREATE INDEX supply_syncer_push_log_group_key_idx + ON supply_syncer_push_log(group_key); + +CREATE INDEX supply_syncer_push_log_server_address_idx + ON supply_syncer_push_log(server_address); + CREATE TABLE supply_update_events ( event_id INTEGER PRIMARY KEY, diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index efbac58f3..a060e6dc9 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -479,7 +479,7 @@ func (q *Queries) QuerySupplyCommitStateMachine(ctx context.Context, groupKey [] } const QuerySupplyCommitment = `-- name: QuerySupplyCommitment :one -SELECT commit_id, group_key, chain_txn_id, output_index, internal_key_id, output_key, block_header, block_height, merkle_proof, supply_root_hash, supply_root_sum +SELECT commit_id, group_key, chain_txn_id, output_index, internal_key_id, output_key, block_header, block_height, merkle_proof, supply_root_hash, supply_root_sum, spent_commitment FROM supply_commitments WHERE commit_id = $1 ` @@ -499,6 +499,7 @@ func (q *Queries) QuerySupplyCommitment(ctx context.Context, commitID int64) (Su &i.MerkleProof, &i.SupplyRootHash, &i.SupplyRootSum, + &i.SpentCommitment, ) return i, err } From d4bf3b94f1dda2ca4cbad9420e401995e0b00ffe Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 6 Aug 2025 19:35:29 +0100 Subject: [PATCH 04/51] tapdb: add SQL queries for supply syncer push log Add the queries InsertSupplySyncerPushLog and FetchSupplySyncerPushLogs to interact with the supply syncer push log table. --- tapdb/sqlc/querier.go | 7 +++ tapdb/sqlc/queries/supply_syncer.sql | 20 +++++++ tapdb/sqlc/supply_syncer.sql.go | 88 ++++++++++++++++++++++++++++ tapdb/universe.go | 10 ++++ 4 files changed, 125 insertions(+) create mode 100644 tapdb/sqlc/queries/supply_syncer.sql create mode 100644 tapdb/sqlc/supply_syncer.sql.go diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 08c71b1b3..44286ae11 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -103,6 +103,9 @@ type Querier interface { FetchSeedlingID(ctx context.Context, arg FetchSeedlingIDParams) (int64, error) FetchSeedlingsForBatch(ctx context.Context, rawKey []byte) ([]FetchSeedlingsForBatchRow, error) FetchSupplyCommit(ctx context.Context, groupKey []byte) (FetchSupplyCommitRow, error) + // Fetches all push log entries for a given asset group, ordered by + // creation time with the most recent entries first. + FetchSupplySyncerPushLogs(ctx context.Context, groupKey []byte) ([]SupplySyncerPushLog, error) // Sort the nodes by node_index here instead of returning the indices. FetchTapscriptTree(ctx context.Context, rootHash []byte) ([]FetchTapscriptTreeRow, error) FetchTransferInputs(ctx context.Context, transferID int64) ([]FetchTransferInputsRow, error) @@ -136,6 +139,10 @@ type Querier interface { InsertRootKey(ctx context.Context, arg InsertRootKeyParams) error InsertSupplyCommitTransition(ctx context.Context, arg InsertSupplyCommitTransitionParams) (int64, error) InsertSupplyCommitment(ctx context.Context, arg InsertSupplyCommitmentParams) (int64, error) + // Inserts a new push log entry to track a successful supply commitment + // push to a remote universe server. The commit_txid and output_index are + // taken directly from the RootCommitment outpoint. + InsertSupplySyncerPushLog(ctx context.Context, arg InsertSupplySyncerPushLogParams) error InsertSupplyUpdateEvent(ctx context.Context, arg InsertSupplyUpdateEventParams) error InsertTxProof(ctx context.Context, arg InsertTxProofParams) error InsertUniverseServer(ctx context.Context, arg InsertUniverseServerParams) error diff --git a/tapdb/sqlc/queries/supply_syncer.sql b/tapdb/sqlc/queries/supply_syncer.sql new file mode 100644 index 000000000..19365765d --- /dev/null +++ b/tapdb/sqlc/queries/supply_syncer.sql @@ -0,0 +1,20 @@ +-- name: InsertSupplySyncerPushLog :exec +-- Inserts a new push log entry to track a successful supply commitment +-- push to a remote universe server. The commit_txid and output_index are +-- taken directly from the RootCommitment outpoint. +INSERT INTO supply_syncer_push_log ( + group_key, max_pushed_block_height, server_address, + commit_txid, output_index, num_leaves_pushed, created_at +) VALUES ( + @group_key, @max_pushed_block_height, @server_address, + @commit_txid, @output_index, @num_leaves_pushed, @created_at +); + +-- name: FetchSupplySyncerPushLogs :many +-- Fetches all push log entries for a given asset group, ordered by +-- creation time with the most recent entries first. +SELECT id, group_key, max_pushed_block_height, server_address, + commit_txid, output_index, num_leaves_pushed, created_at +FROM supply_syncer_push_log +WHERE group_key = @group_key +ORDER BY created_at DESC; diff --git a/tapdb/sqlc/supply_syncer.sql.go b/tapdb/sqlc/supply_syncer.sql.go new file mode 100644 index 000000000..8eefd56e4 --- /dev/null +++ b/tapdb/sqlc/supply_syncer.sql.go @@ -0,0 +1,88 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: supply_syncer.sql + +package sqlc + +import ( + "context" +) + +const FetchSupplySyncerPushLogs = `-- name: FetchSupplySyncerPushLogs :many +SELECT id, group_key, max_pushed_block_height, server_address, + commit_txid, output_index, num_leaves_pushed, created_at +FROM supply_syncer_push_log +WHERE group_key = $1 +ORDER BY created_at DESC +` + +// Fetches all push log entries for a given asset group, ordered by +// creation time with the most recent entries first. +func (q *Queries) FetchSupplySyncerPushLogs(ctx context.Context, groupKey []byte) ([]SupplySyncerPushLog, error) { + rows, err := q.db.QueryContext(ctx, FetchSupplySyncerPushLogs, groupKey) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SupplySyncerPushLog + for rows.Next() { + var i SupplySyncerPushLog + if err := rows.Scan( + &i.ID, + &i.GroupKey, + &i.MaxPushedBlockHeight, + &i.ServerAddress, + &i.CommitTxid, + &i.OutputIndex, + &i.NumLeavesPushed, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const InsertSupplySyncerPushLog = `-- name: InsertSupplySyncerPushLog :exec +INSERT INTO supply_syncer_push_log ( + group_key, max_pushed_block_height, server_address, + commit_txid, output_index, num_leaves_pushed, created_at +) VALUES ( + $1, $2, $3, + $4, $5, $6, $7 +) +` + +type InsertSupplySyncerPushLogParams struct { + GroupKey []byte + MaxPushedBlockHeight int32 + ServerAddress string + CommitTxid []byte + OutputIndex int32 + NumLeavesPushed int32 + CreatedAt int64 +} + +// Inserts a new push log entry to track a successful supply commitment +// push to a remote universe server. The commit_txid and output_index are +// taken directly from the RootCommitment outpoint. +func (q *Queries) InsertSupplySyncerPushLog(ctx context.Context, arg InsertSupplySyncerPushLogParams) error { + _, err := q.db.ExecContext(ctx, InsertSupplySyncerPushLog, + arg.GroupKey, + arg.MaxPushedBlockHeight, + arg.ServerAddress, + arg.CommitTxid, + arg.OutputIndex, + arg.NumLeavesPushed, + arg.CreatedAt, + ) + return err +} diff --git a/tapdb/universe.go b/tapdb/universe.go index 15a1822de..54678177a 100644 --- a/tapdb/universe.go +++ b/tapdb/universe.go @@ -138,6 +138,16 @@ type BaseUniverseStore interface { QuerySupplyLeavesByHeight(ctx context.Context, arg QuerySupplyLeavesByHeightParams) ( []sqlc.QuerySupplyLeavesByHeightRow, error) + + // InsertSupplySyncerPushLog inserts a supply syncer push log entry to + // track a successful push to a remote universe server. + InsertSupplySyncerPushLog(ctx context.Context, + arg sqlc.InsertSupplySyncerPushLogParams) error + + // FetchSupplySyncerPushLogs fetches all push log entries for + // a given asset group, ordered by creation time (newest first). + FetchSupplySyncerPushLogs(ctx context.Context, + groupKey []byte) ([]sqlc.SupplySyncerPushLog, error) } // getUniverseTreeSum retrieves the sum of a universe tree specified by its From 7568636f44383aaabfcb1d10f6d93820c083528c Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 6 Aug 2025 17:39:11 +0100 Subject: [PATCH 05/51] tapdb: add SupplySyncerStore Add a store used by the syncer to persist push logs. --- tapdb/supply_commit_test.go | 133 ++++++++++++++++++++++++++++++++++++ tapdb/supply_syncer.go | 105 ++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 tapdb/supply_syncer.go diff --git a/tapdb/supply_commit_test.go b/tapdb/supply_commit_test.go index 1489de4cb..88b2b6753 100644 --- a/tapdb/supply_commit_test.go +++ b/tapdb/supply_commit_test.go @@ -18,6 +18,7 @@ import ( "github.com/lightninglabs/taproot-assets/mssmt" "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" + "github.com/lightninglabs/taproot-assets/universe" "github.com/lightninglabs/taproot-assets/universe/supplycommit" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" @@ -2071,6 +2072,138 @@ func encodeTx(tx *wire.MsgTx) ([]byte, error) { return buf.Bytes(), err } +// TestSupplySyncerPushLog tests the LogSupplyCommitPush method which logs +// successful pushes to remote universe servers. +func TestSupplySyncerPushLog(t *testing.T) { + t.Parallel() + + // Set up the test harness with all necessary components. + h := newSupplyCommitTestHarness(t) + + // Create a test supply commitment that we can reference. + // Use the same simple approach as + // TestSupplyCommitMultipleSupplyCommitments. + genTxData := func() (int64, []byte, []byte) { + genesisPoint := test.RandOp(h.t) + tx := wire.NewMsgTx(2) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: genesisPoint, + }) + tx.AddTxOut(&wire.TxOut{ + Value: 1000, + PkScript: test.RandBytes(20), + }) + + txBytes, err := encodeTx(tx) + require.NoError(h.t, err) + txid := tx.TxHash() + chainTxID, err := h.db.UpsertChainTx( + h.ctx, sqlc.UpsertChainTxParams{ + Txid: txid[:], + RawTx: txBytes, + }, + ) + require.NoError(h.t, err) + return chainTxID, txid[:], txBytes + } + + chainTxID, txid, rawTx := genTxData() + commitID := h.addTestSupplyCommitment(chainTxID, txid, rawTx, false) + + // Get the supply root that was created by addTestSupplyCommitment. + rows, err := h.db.(sqlc.DBTX).QueryContext(h.ctx, ` + SELECT supply_root_hash, supply_root_sum FROM supply_commitments + WHERE commit_id = $1 + `, commitID) + require.NoError(t, err) + defer rows.Close() + require.True(t, rows.Next(), "Expected supply commitment to exist") + + var rootHashBytes []byte + var rootSum int64 + err = rows.Scan(&rootHashBytes, &rootSum) + require.NoError(t, err) + require.NoError(t, rows.Close()) + + var rootHash mssmt.NodeHash + copy(rootHash[:], rootHashBytes) + + // Decode the raw transaction to get the actual wire.MsgTx used in the + // test data. + var actualTx wire.MsgTx + err = actualTx.Deserialize(bytes.NewReader(rawTx)) + require.NoError(t, err) + + // Create a SupplySyncerStore and test the actual LogSupplyCommitPush + // method. + syncerStore := NewSupplySyncerStore(h.batchedTreeDB) + + // Create mock data for the method call. + serverAddr := universe.NewServerAddrFromStr("localhost:8080") + supplyRoot := mssmt.NewComputedBranch(rootHash, uint64(rootSum)) + + // Create minimal supply leaves - just need something to count. + // We need at least one leaf or the method returns early without + // logging. + mintEvent := supplycommit.NewMintEvent{ + MintHeight: 100, + } + leaves := supplycommit.SupplyLeaves{ + IssuanceLeafEntries: []supplycommit.NewMintEvent{mintEvent}, + } + + commitment := supplycommit.RootCommitment{ + SupplyRoot: supplyRoot, + Txn: &actualTx, + TxOutIdx: 0, + InternalKey: keychain.KeyDescriptor{PubKey: h.groupPubKey}, + OutputKey: h.groupPubKey, + } + + // Record the time before the call to verify timestamp is recent. + beforeCall := time.Now().Unix() + + // Test the actual LogSupplyCommitPush method. + err = syncerStore.LogSupplyCommitPush( + h.ctx, serverAddr, h.assetSpec, commitment, leaves, + ) + require.NoError(t, err, "LogSupplyCommitPush should work") + + afterCall := time.Now().Unix() + + // Verify the log entry was created correctly using the new fetch query. + var logEntries []sqlc.SupplySyncerPushLog + readTx := ReadTxOption() + err = h.batchedTreeDB.ExecTx(h.ctx, readTx, + func(dbTx BaseUniverseStore) error { + var txErr error + logEntries, txErr = dbTx.FetchSupplySyncerPushLogs( + h.ctx, h.groupKeyBytes, + ) + return txErr + }, + ) + require.NoError(t, err) + require.Len(t, logEntries, 1, "Expected exactly one push log entry") + + logEntry := logEntries[0] + + // Verify all the fields are correct. + require.Equal(t, h.groupKeyBytes, logEntry.GroupKey) + require.Equal(t, int32(100), logEntry.MaxPushedBlockHeight) + require.Equal(t, "localhost:8080", logEntry.ServerAddress) + require.Equal(t, txid, logEntry.CommitTxid) + require.Equal(t, int32(0), logEntry.OutputIndex) + require.Equal(t, int32(1), logEntry.NumLeavesPushed) + require.GreaterOrEqual(t, logEntry.CreatedAt, beforeCall) + require.LessOrEqual(t, logEntry.CreatedAt, afterCall) + + t.Logf("Successfully logged push: commitTxid=%x, outputIndex=%d, "+ + "timestamp=%d, leaves=%d", logEntry.CommitTxid, + logEntry.OutputIndex, logEntry.CreatedAt, + logEntry.NumLeavesPushed) +} + // assertEqualEvents compares two supply update events by serializing them and // comparing the resulting bytes. func assertEqualEvents(t *testing.T, expected, diff --git a/tapdb/supply_syncer.go b/tapdb/supply_syncer.go new file mode 100644 index 000000000..ee4c3ecef --- /dev/null +++ b/tapdb/supply_syncer.go @@ -0,0 +1,105 @@ +package tapdb + +import ( + "context" + "fmt" + "time" + + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/tapdb/sqlc" + "github.com/lightninglabs/taproot-assets/universe" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" +) + +// SupplySyncerStore implements the persistent storage for supply syncing +// operations. It provides methods to store supply updates without requiring +// a supply commitment transition. +type SupplySyncerStore struct { + db BatchedUniverseTree +} + +// NewSupplySyncerStore creates a new supply syncer DB store handle. +func NewSupplySyncerStore(db BatchedUniverseTree) *SupplySyncerStore { + return &SupplySyncerStore{ + db: db, + } +} + +// LogSupplyCommitPush logs that a supply commitment and its leaves +// have been successfully pushed to a remote universe server. +func (s *SupplySyncerStore) LogSupplyCommitPush(ctx context.Context, + serverAddr universe.ServerAddr, assetSpec asset.Specifier, + commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error { + + // Calculate the total number of leaves in this push. + numLeaves := int32(len(leaves.IssuanceLeafEntries) + + len(leaves.BurnLeafEntries) + + len(leaves.IgnoreLeafEntries)) + + // If no leaves were provided, return early without error. + if numLeaves == 0 { + return nil + } + + // Find the highest block height from all the supply leaves. + var maxBlockHeight uint32 + for _, leafEntry := range leaves.IssuanceLeafEntries { + if height := leafEntry.BlockHeight(); height > maxBlockHeight { + maxBlockHeight = height + } + } + for _, leafEntry := range leaves.BurnLeafEntries { + if height := leafEntry.BlockHeight(); height > maxBlockHeight { + maxBlockHeight = height + } + } + for _, leafEntry := range leaves.IgnoreLeafEntries { + if height := leafEntry.BlockHeight(); height > maxBlockHeight { + maxBlockHeight = height + } + } + + // All leaves must have a valid block height. + if maxBlockHeight == 0 { + return fmt.Errorf("all supply leaves must have a valid " + + "block height greater than 0") + } + + // Extract the group key for the log entry. + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return fmt.Errorf("group key must be specified for supply "+ + "syncer log: %w", err) + } + + groupKeyBytes := groupKey.SerializeCompressed() + + // Extract the outpoint (transaction ID and output index) from the + // commitment. + commitTxid := commitment.Txn.TxHash() + outputIndex := commitment.TxOutIdx + + var writeTx BaseUniverseStoreOptions + return s.db.ExecTx(ctx, &writeTx, func(dbTx BaseUniverseStore) error { + // Insert the push log entry. The SQL query will find the + // chain_txn_id by looking up the supply commitment using the + // commitment transaction hash and output index (outpoint). + params := sqlc.InsertSupplySyncerPushLogParams{ + GroupKey: groupKeyBytes, + MaxPushedBlockHeight: int32(maxBlockHeight), + ServerAddress: serverAddr.HostStr(), + CommitTxid: commitTxid[:], + OutputIndex: int32(outputIndex), + NumLeavesPushed: numLeaves, + CreatedAt: time.Now().Unix(), + } + err := dbTx.InsertSupplySyncerPushLog(ctx, params) + if err != nil { + return fmt.Errorf("failed to log supply commit push: "+ + "%w", err) + } + + return nil + }) +} From dff7c5ffe523647726ae89233126ee563b7c33b7 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 20 Aug 2025 21:46:20 +0100 Subject: [PATCH 06/51] proof: rename txSpendsPrevOut for export Renamed the function to make it available for use in the supply commit package in a subsequent commit. --- proof/util.go | 4 ++-- proof/verifier.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proof/util.go b/proof/util.go index fdf60b6b9..b7c6cc5fb 100644 --- a/proof/util.go +++ b/proof/util.go @@ -45,9 +45,9 @@ func unpackBits(bytes []byte) []bool { return bits } -// txSpendsPrevOut returns whether the given prevout is spent by the given +// TxSpendsPrevOut returns whether the given prevout is spent by the given // transaction. -func txSpendsPrevOut(tx *wire.MsgTx, prevOut *wire.OutPoint) bool { +func TxSpendsPrevOut(tx *wire.MsgTx, prevOut *wire.OutPoint) bool { for _, txIn := range tx.TxIn { if txIn.PreviousOutPoint == *prevOut { return true diff --git a/proof/verifier.go b/proof/verifier.go index 78bae3706..c83cfce2f 100644 --- a/proof/verifier.go +++ b/proof/verifier.go @@ -946,7 +946,7 @@ func (p *Proof) VerifyProofIntegrity(ctx context.Context, vCtx VerifierCtx, // 1. A transaction that spends the previous asset output has a valid // merkle proof within a block in the chain. - if !txSpendsPrevOut(&p.AnchorTx, &p.PrevOut) { + if !TxSpendsPrevOut(&p.AnchorTx, &p.PrevOut) { return nil, fmt.Errorf("%w: doesn't spend prev output", commitment.ErrInvalidTaprootProof) } From d92ae38c1ecda65602faa3a9ac401d54ccbc5ece Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 22 Aug 2025 12:37:45 +0100 Subject: [PATCH 07/51] supplycommit: rename applyTreeUpdates for export Renames applyTreeUpdates to make it suitable for reuse in supplyverifier during the verification process. --- universe/supplycommit/transitions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index 70f55d4b2..7b337ea3c 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -207,9 +207,9 @@ func insertIntoTree(tree mssmt.Tree, leafKey [32]byte, return tree.Insert(ctx, leafKey, leafValue) } -// applyTreeUpdates takes the set of pending updates, and applies them to the +// ApplyTreeUpdates takes the set of pending updates, and applies them to the // given supply trees. It returns a new map containing the updated trees. -func applyTreeUpdates(supplyTrees SupplyTrees, +func ApplyTreeUpdates(supplyTrees SupplyTrees, pendingUpdates []SupplyUpdateEvent) (SupplyTrees, error) { ctx := context.Background() @@ -332,7 +332,7 @@ func (c *CommitTreeCreateState) ProcessEvent(event Event, // Next, based on the type of event, we'll create a new key+leaf // to insert into the respective sub-tree. - newSupplyTrees, err := applyTreeUpdates( + newSupplyTrees, err := ApplyTreeUpdates( oldSupplyTrees, pendingUpdates, ) if err != nil { From 1802abc98420e91d0515021417d4cb521dafc397 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 22 Aug 2025 13:59:03 +0100 Subject: [PATCH 08/51] supplycommit: add UpdateRootSupplyTree for verifier use Refactor existing logic into a standalone exported function, UpdateRootSupplyTree, to enable reuse in the `supplyverifier` package for verification purposes. --- universe/supplycommit/transitions.go | 71 +++++++++++++++++----------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index 7b337ea3c..4204755b3 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -262,6 +262,44 @@ func ApplyTreeUpdates(supplyTrees SupplyTrees, return updatedSupplyTrees, nil } +// UpdateRootSupplyTree takes the given root supply tree, and updates it with +// the set of subtrees. It returns a new tree instance with the updated values. +func UpdateRootSupplyTree(ctx context.Context, rootTree mssmt.Tree, + subTrees SupplyTrees) (mssmt.Tree, error) { + + updatedRoot := rootTree + + // Now we'll insert/update each of the read subtrees into the root + // supply tree. + for treeType, subTree := range subTrees { + subTreeRoot, err := subTree.Root(ctx) + if err != nil { + return nil, fmt.Errorf("unable to fetch "+ + "sub-tree root: %w", err) + } + + if subTreeRoot.NodeSum() == 0 { + continue + } + + rootTreeLeaf := mssmt.NewLeafNode( + lnutils.ByteSlice(subTreeRoot.NodeHash()), + subTreeRoot.NodeSum(), + ) + + rootTreeKey := treeType.UniverseKey() + updatedRoot, err = insertIntoTree( + updatedRoot, rootTreeKey, rootTreeLeaf, + ) + if err != nil { + return nil, fmt.Errorf("unable to insert "+ + "sub-tree into root supply tree: %w", err) + } + } + + return updatedRoot, nil +} + // ProcessEvent processes incoming events for the CommitTreeCreateState. From // this state, we'll take the set of pending changes, then create/read the // components of the sub-supply trees, then use that to create the new finalized @@ -351,33 +389,12 @@ func (c *CommitTreeCreateState) ProcessEvent(event Event, "supply tree: %w", err) } - // Now we'll insert/update each of the read sub-trees into the - // root supply tree. - for treeType, subTree := range newSupplyTrees { - subTreeRoot, err := subTree.Root(ctx) - if err != nil { - return nil, fmt.Errorf("unable to fetch "+ - "sub-tree root: %w", err) - } - - if subTreeRoot.NodeSum() == 0 { - continue - } - - rootTreeLeaf := mssmt.NewLeafNode( - lnutils.ByteSlice(subTreeRoot.NodeHash()), - subTreeRoot.NodeSum(), - ) - - rootTreeKey := treeType.UniverseKey() - rootSupplyTree, err = insertIntoTree( - rootSupplyTree, rootTreeKey, rootTreeLeaf, - ) - if err != nil { - return nil, fmt.Errorf("unable to insert "+ - "sub-tree into root supply tree: %w", - err) - } + rootSupplyTree, err = UpdateRootSupplyTree( + ctx, rootSupplyTree, newSupplyTrees, + ) + if err != nil { + return nil, fmt.Errorf("unable to update root "+ + "supply tree: %w", err) } // Construct the state transition object. We'll begin to From bb11776b5e4dec247784311d6863eb09650517b4 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 22 Aug 2025 17:31:56 +0100 Subject: [PATCH 09/51] tapdb: extract fetchRootSupplyTreeInternal from FetchRootSupplyTree This refactor enables reuse of the function in a new method to be introduced in a subsequent commit. The upcoming method will support reading both the sbtrees and the (upper) root tree from the database atomically. --- tapdb/supply_tree.go | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/tapdb/supply_tree.go b/tapdb/supply_tree.go index 549b51813..02b7e5e5d 100644 --- a/tapdb/supply_tree.go +++ b/tapdb/supply_tree.go @@ -233,6 +233,30 @@ func (s *SupplyTreeStore) FetchSubTrees(ctx context.Context, return lfn.Ok(trees) } +// fetchRootSupplyTreeInternal fetches and copies the root supply tree within +// an existing database transaction. Returned tree is a copy in memory. +func fetchRootSupplyTreeInternal(ctx context.Context, db BaseUniverseStore, + groupKey *btcec.PublicKey) (mssmt.Tree, error) { + + rootNs := rootSupplyNamespace(groupKey) + + // Create a wrapper for the persistent tree store. + persistentStore := newTreeStoreWrapperTx(db, rootNs) + persistentTree := mssmt.NewCompactedTree(persistentStore) + + // Create a new in-memory tree to copy into. + memTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore()) + + // Copy the persistent tree to the in-memory tree. + err := persistentTree.Copy(ctx, memTree) + if err != nil { + return nil, fmt.Errorf("unable to copy root supply "+ + "tree %s: %w", rootNs, err) + } + + return memTree, nil +} + // FetchRootSupplyTree returns a copy of the root supply tree for the given // asset spec. func (s *SupplyTreeStore) FetchRootSupplyTree(ctx context.Context, @@ -245,24 +269,13 @@ func (s *SupplyTreeStore) FetchRootSupplyTree(ctx context.Context, ) } - rootNs := rootSupplyNamespace(groupKey) - var treeCopy mssmt.Tree readTx := NewBaseUniverseReadTx() err = s.db.ExecTx(ctx, &readTx, func(db BaseUniverseStore) error { - // Create a wrapper for the persistent tree store. - persistentStore := newTreeStoreWrapperTx(db, rootNs) - persistentTree := mssmt.NewCompactedTree(persistentStore) - - // Create a new in-memory tree to copy into. - memTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore()) - - // Copy the persistent tree to the in-memory tree. - err := persistentTree.Copy(ctx, memTree) + memTree, err := fetchRootSupplyTreeInternal(ctx, db, groupKey) if err != nil { - return fmt.Errorf("unable to copy root supply "+ - "tree %s: %w", rootNs, err) + return err } treeCopy = memTree From a06697c9c55cdb3177818b56e11258fb884b7cc6 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 22 Aug 2025 17:40:04 +0100 Subject: [PATCH 10/51] tapdb: add FetchSupplyTrees to retrieve all supply trees atomically Introduces a new method to retrieve all supply trees for a given asset group in a single atomic database read. --- tapdb/supply_tree.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tapdb/supply_tree.go b/tapdb/supply_tree.go index 02b7e5e5d..8a0eb6164 100644 --- a/tapdb/supply_tree.go +++ b/tapdb/supply_tree.go @@ -288,6 +288,55 @@ func (s *SupplyTreeStore) FetchRootSupplyTree(ctx context.Context, return lfn.Ok(treeCopy) } +// FetchSupplyTrees returns a copy of the root supply tree and subtrees for the +// given asset spec. +func (s *SupplyTreeStore) FetchSupplyTrees(ctx context.Context, + spec asset.Specifier) (mssmt.Tree, *supplycommit.SupplyTrees, error) { + + groupKey, err := spec.UnwrapGroupKeyOrErr() + if err != nil { + return nil, nil, fmt.Errorf( + "group key must be specified for supply tree: %w", err, + ) + } + + var ( + rootTree mssmt.Tree + subTrees = make(supplycommit.SupplyTrees) + ) + + readTx := NewBaseUniverseReadTx() + err = s.db.ExecTx(ctx, &readTx, func(db BaseUniverseStore) error { + // Fetch the root supply tree. + memTree, err := fetchRootSupplyTreeInternal(ctx, db, groupKey) + if err != nil { + return err + } + + rootTree = memTree + + // Fetch all the subtrees. + for _, treeType := range allSupplyTreeTypes { + subTree, fetchErr := fetchSubTreeInternal( + ctx, db, groupKey, treeType, + ) + if fetchErr != nil { + return fmt.Errorf("failed to fetch subtree "+ + "%v: %w", treeType, fetchErr) + } + + subTrees[treeType] = subTree + } + + return nil + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to execute db tx: %w", err) + } + + return rootTree, &subTrees, nil +} + // registerMintSupplyInternal inserts a new minting leaf into the mint supply // sub-tree within an existing database transaction. It returns the universe // proof containing the new sub-tree root. From fa1443973baa48845635ac1300954597d0ccb05f Mon Sep 17 00:00:00 2001 From: ffranr Date: Sat, 23 Aug 2025 00:33:33 +0100 Subject: [PATCH 11/51] supplyverifier: add placeholder supply verifier state machine manager Introduces a manager with an InsertSupplyCommit method to handle supply commit verification and local DB insertion for universe server tapd nodes. Subsequent commits will further develop this component. The manager follows the same pattern as the supplycommit package's state machine manager. Each state machine is tied to an asset group, and the manager oversees all machines across groups. We avoid specifying the full state machine now, as it won't be used for universe server supply commit verification, which will be implemented first. This placeholder enables progress on the RPC implementation in upcoming commits. --- universe/supplyverifier/env.go | 52 ++++ universe/supplyverifier/manager.go | 412 +++++++++++++++++++++++++++++ universe/supplyverifier/states.go | 62 +++++ 3 files changed, 526 insertions(+) create mode 100644 universe/supplyverifier/env.go create mode 100644 universe/supplyverifier/manager.go create mode 100644 universe/supplyverifier/states.go diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go new file mode 100644 index 000000000..c168ea3ad --- /dev/null +++ b/universe/supplyverifier/env.go @@ -0,0 +1,52 @@ +package supplyverifier + +import ( + "context" + "fmt" + + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/tapgarden" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" + lfn "github.com/lightningnetwork/lnd/fn/v2" +) + +// SupplyCommitView is an interface that is used to look up supply commitments +// and pre-commitments. +type SupplyCommitView interface { + // UnspentPrecommits returns the set of unspent pre-commitments for a + // given asset spec. + UnspentPrecommits(ctx context.Context, + assetSpec asset.Specifier) lfn.Result[supplycommit.PreCommits] + + // SupplyCommit returns the latest supply commitment for a given asset + // spec. + SupplyCommit(ctx context.Context, + assetSpec asset.Specifier) supplycommit.RootCommitResp +} + +// Environment is a struct that holds all the dependencies that the supply +// verifier needs to carry out its duties. +type Environment struct { + // AssetSpec is the asset specifier that is used to identify the asset + // that we're maintaining a supply commit for. + AssetSpec asset.Specifier + + // Chain is our access to the current main chain. + Chain tapgarden.ChainBridge + + // SupplyCommitView allows us to look up supply commitments and + // pre-commitments. + SupplyCommitView SupplyCommitView + + // ErrChan is the channel that is used to send errors to the caller. + ErrChan chan<- error + + // QuitChan is the channel that is used to signal that the state + // machine should quit. + QuitChan <-chan struct{} +} + +// Name returns the name of the environment. +func (e *Environment) Name() string { + return fmt.Sprintf("supply_verifier(%s)", e.AssetSpec.String()) +} diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go new file mode 100644 index 000000000..b3abba6e9 --- /dev/null +++ b/universe/supplyverifier/manager.go @@ -0,0 +1,412 @@ +package supplyverifier + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/tapgarden" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightningnetwork/lnd/msgmux" + "github.com/lightningnetwork/lnd/protofsm" +) + +const ( + // DefaultTimeout is the context guard default timeout. + DefaultTimeout = 30 * time.Second +) + +// DaemonAdapters is a wrapper around the protofsm.DaemonAdapters interface +// with the addition of Start and Stop methods. +type DaemonAdapters interface { + protofsm.DaemonAdapters + + // Start starts the daemon adapters handler service. + Start() error + + // Stop stops the daemon adapters handler service. + Stop() error +} + +// StateMachineStore is an interface that allows the state machine to persist +// its state across restarts. This is used to track the state of the state +// machine for supply verification. +type StateMachineStore interface { + // CommitState is used to commit the state of the state machine to disk. + CommitState(context.Context, asset.Specifier, State) error + + // FetchState attempts to fetch the state of the state machine for the + // target asset specifier. If the state machine doesn't exist, then a + // default state will be returned. + FetchState(context.Context, asset.Specifier) (State, error) +} + +// IssuanceSubscriptions allows verifier state machines to subscribe to +// asset group issuance events. +type IssuanceSubscriptions interface { + // RegisterSubscriber registers an event receiver to receive future + // issuance events. + RegisterSubscriber(receiver *fn.EventReceiver[fn.Event], + deliverExisting bool, _ bool) error +} + +// ManagerCfg is the configuration for the +// Manager. It contains all the dependencies needed to +// manage multiple supply verifier state machines, one for each asset group. +type ManagerCfg struct { + // Chain is our access to the current main chain. + Chain tapgarden.ChainBridge + + // SupplyCommitView allows us to look up supply commitments and + // pre-commitments. + SupplyCommitView SupplyCommitView + + // SupplySyncer is used to retrieve supply leaves from a universe and + // persist them to the local database. + SupplySyncer SupplySyncer + + // IssuanceSubscriptions registers verifier state machines to receive + // new asset group issuance event notifications. + IssuanceSubscriptions IssuanceSubscriptions + + // DaemonAdapters is a set of adapters that allow the state machine to + // interact with external daemons whilst processing internal events. + DaemonAdapters DaemonAdapters + + // StateLog is the main state log that is used to track the state of the + // state machine. This is used to persist the state of the state machine + // across restarts. + StateLog StateMachineStore + + // ErrChan is the channel that is used to send errors to the caller. + ErrChan chan<- error +} + +// Manager is a manager for multiple supply verifier state machines, one for +// each asset group. It is responsible for starting and stopping the state +// machines, as well as forwarding events to them. +type Manager struct { + // cfg is the configuration for the multi state machine manager. + cfg ManagerCfg + + // smCache is a cache that maps asset group public keys to their + // supply verifier state machines. + smCache *stateMachineCache + + // ContextGuard provides a wait group and main quit channel that can be + // used to create guarded contexts. + *fn.ContextGuard + + startOnce sync.Once + stopOnce sync.Once +} + +// NewManager creates a new multi state machine manager. +func NewManager(cfg ManagerCfg) *Manager { + return &Manager{ + cfg: cfg, + ContextGuard: &fn.ContextGuard{ + DefaultTimeout: DefaultTimeout, + Quit: make(chan struct{}), + }, + } +} + +// Start starts the multi state machine manager. +func (m *Manager) Start() error { + m.startOnce.Do(func() { + // Initialize the state machine cache. + m.smCache = newStateMachineCache() + }) + + return nil +} + +// Stop stops the multi state machine manager, which in turn stops all asset +// group key specific supply verifier state machines. +func (m *Manager) Stop() error { + m.stopOnce.Do(func() { + // Cancel the state machine context to signal all state machines + // to stop. + close(m.Quit) + + // Stop all state machines. + m.smCache.StopAll() + }) + + return nil +} + +// fetchStateMachine retrieves a state machine from the cache or creates a +// new one if it doesn't exist. If a new state machine is created, it is also +// started. +func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, + error) { + + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return nil, fmt.Errorf("asset specifier missing group key: %w", + err) + } + + // Check if the state machine for the asset group already exists in the + // cache. + sm, ok := m.smCache.Get(*groupKey) + if ok { + return sm, nil + } + + // If the state machine is not found, create a new one. + env := &Environment{ + AssetSpec: assetSpec, + Chain: m.cfg.Chain, + SupplyCommitView: m.cfg.SupplyCommitView, + ErrChan: m.cfg.ErrChan, + QuitChan: m.Quit, + } + + // Before we start the state machine, we'll need to fetch the current + // state from disk, to see if we need to emit any new events. + ctx, cancel := m.WithCtxQuitNoTimeout() + defer cancel() + + initialState, err := m.cfg.StateLog.FetchState(ctx, assetSpec) + if err != nil { + return nil, fmt.Errorf("unable to fetch current state: %w", err) + } + + // Create a new error reporter for the state machine. + errorReporter := NewErrorReporter(assetSpec) + + fsmCfg := protofsm.StateMachineCfg[Event, *Environment]{ + ErrorReporter: &errorReporter, + InitialState: initialState, + Env: env, + Daemon: m.cfg.DaemonAdapters, + } + newSm := protofsm.NewStateMachine[Event, *Environment](fsmCfg) + + // Ensure that the state machine is running. We use the manager's + // context guard to derive a sub context which will be cancelled when + // the manager is stopped. + smCtx, _ := m.WithCtxQuitNoTimeout() + newSm.Start(smCtx) + + // For supply verifier, we always start with an InitEvent to begin + // the verification process. + newSm.SendEvent(ctx, &InitEvent{}) + + m.smCache.Set(*groupKey, &newSm) + + return &newSm, nil +} + +// InsertSupplyCommit stores a verified supply commitment for the given asset +// group in the node's local database. +func (m *Manager) InsertSupplyCommit(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves, + chainProof supplycommit.ChainProof) error { + + // TODO(ffranr): Verify supply commit without starting a state machine. + // This is effectively where universe server supply commit verification + // takes place. Once verified, we can store the commitment in the + // local database. + + return nil +} + +// CanHandle determines if the state machine associated with the given asset +// specifier can handle the given message. If a state machine for the asset +// group does not exist, it will be created and started. +func (m *Manager) CanHandle(assetSpec asset.Specifier, + msg msgmux.PeerMsg) (bool, error) { + + sm, err := m.fetchStateMachine(assetSpec) + if err != nil { + return false, fmt.Errorf("unable to get or create state "+ + "machine: %w", err) + } + + return sm.CanHandle(msg), nil +} + +// Name returns the name of the state machine associated with the given asset +// specifier. If a state machine for the asset group does not exist, it will be +// created and started. +func (m *Manager) Name(assetSpec asset.Specifier) (string, error) { + sm, err := m.fetchStateMachine(assetSpec) + if err != nil { + return "", fmt.Errorf("unable to get or create state "+ + "machine: %w", err) + } + + return sm.Name(), nil +} + +// SendMessage sends a message to the state machine associated with the given +// asset specifier. If a state machine for the asset group does not exist, it +// will be created and started. +func (m *Manager) SendMessage(ctx context.Context, + assetSpec asset.Specifier, msg msgmux.PeerMsg) (bool, error) { + + sm, err := m.fetchStateMachine(assetSpec) + if err != nil { + return false, fmt.Errorf("unable to get or create state "+ + "machine: %w", err) + } + + return sm.SendMessage(ctx, msg), nil +} + +// CurrentState returns the current state of the state machine associated with +// the given asset specifier. If a state machine for the asset group does not +// exist, it will be created and started. +func (m *Manager) CurrentState(assetSpec asset.Specifier) ( + protofsm.State[Event, *Environment], error) { + + sm, err := m.fetchStateMachine(assetSpec) + if err != nil { + return nil, fmt.Errorf("unable to get or create state "+ + "machine: %w", err) + } + + return sm.CurrentState() +} + +// RegisterStateEvents registers a state event subscriber with the state machine +// associated with the given asset specifier. If a state machine for the asset +// group does not exist, it will be created and started. +func (m *Manager) RegisterStateEvents( + assetSpec asset.Specifier) (StateSub, error) { + + sm, err := m.fetchStateMachine(assetSpec) + if err != nil { + return nil, fmt.Errorf("unable to get or create state "+ + "machine: %w", err) + } + + return sm.RegisterStateEvents(), nil +} + +// RemoveStateSub removes a state event subscriber from the state machine +// associated with the given asset specifier. If a state machine for the asset +// group does not exist, it will be created and started. +func (m *Manager) RemoveStateSub(assetSpec asset.Specifier, + sub StateSub) error { + + sm, err := m.fetchStateMachine(assetSpec) + if err != nil { + return fmt.Errorf("unable to get or create state "+ + "machine: %w", err) + } + + sm.RemoveStateSub(sub) + + return nil +} + +// stateMachineCache is a thread-safe cache mapping an asset group's public key +// to its supply verifier state machine. +type stateMachineCache struct { + // mu is a mutex that is used to synchronize access to the cache. + mu sync.RWMutex + + // cache is a map of serialized asset group public keys to their + // supply verifier state machines. + cache map[asset.SerializedKey]*StateMachine +} + +// newStateMachineCache creates a new supply verifier state machine cache. +func newStateMachineCache() *stateMachineCache { + return &stateMachineCache{ + cache: make(map[asset.SerializedKey]*StateMachine), + } +} + +// StopAll stops all state machines in the cache. +func (c *stateMachineCache) StopAll() { + c.mu.RLock() + defer c.mu.RUnlock() + + // Iterate over the cache and append each state machine to the slice. + for _, sm := range c.cache { + // Sanity check: ensure sm is not nil. + if sm == nil { + continue + } + + // Stop the state machine. + sm.Stop() + } +} + +// Get retrieves a state machine from the cache. +func (c *stateMachineCache) Get(groupPubKey btcec.PublicKey) (*StateMachine, + bool) { + + // Serialize the group key. + serializedGroupKey := asset.ToSerialized(&groupPubKey) + + c.mu.RLock() + defer c.mu.RUnlock() + + sm, ok := c.cache[serializedGroupKey] + return sm, ok +} + +// Set adds a state machine to the cache. +func (c *stateMachineCache) Set(groupPubKey btcec.PublicKey, sm *StateMachine) { + // Serialize the group key. + serializedGroupKey := asset.ToSerialized(&groupPubKey) + + c.mu.Lock() + defer c.mu.Unlock() + + // If the state machine already exists, return without updating it. + // This helps to ensure that we always have a pointer to every state + // machine in the cache, even if it is not currently active. + if _, exists := c.cache[serializedGroupKey]; exists { + return + } + + c.cache[serializedGroupKey] = sm +} + +// Delete removes a state machine from the cache. +func (c *stateMachineCache) Delete(groupPubKey btcec.PublicKey) { + // Serialize the group key. + serializedGroupKey := asset.ToSerialized(&groupPubKey) + + c.mu.Lock() + defer c.mu.Unlock() + + delete(c.cache, serializedGroupKey) +} + +// ErrorReporter is an asset specific error reporter that can be used to +// report errors that occur during the operation of the asset group supply +// verifier state machine. +type ErrorReporter struct { + // assetSpec is the asset specifier that identifies the asset group. + assetSpec asset.Specifier +} + +// NewErrorReporter creates a new ErrorReporter for the given asset specifier +// state machine. +func NewErrorReporter(assetSpec asset.Specifier) ErrorReporter { + return ErrorReporter{ + assetSpec: assetSpec, + } +} + +// ReportError reports an error that occurred during the operation of the +// asset group supply verifier state machine. +func (r *ErrorReporter) ReportError(err error) { + log.Errorf("supply verifier state machine (asset_spec=%s): %v", + r.assetSpec.String(), err) +} diff --git a/universe/supplyverifier/states.go b/universe/supplyverifier/states.go new file mode 100644 index 000000000..1bc5f04a1 --- /dev/null +++ b/universe/supplyverifier/states.go @@ -0,0 +1,62 @@ +package supplyverifier + +import ( + "fmt" + + "github.com/lightningnetwork/lnd/protofsm" +) + +var ( + // ErrInvalidStateTransition is returned when we receive an unexpected + // event for a given state. + ErrInvalidStateTransition = fmt.Errorf("invalid state transition") +) + +// Event is a special interface used to create the equivalent of a sum-type, but +// using a "sealed" interface. +type Event interface { + eventSealed() +} + +// Events is a special type constraint that enumerates all the possible protocol +// events. +type Events interface { +} + +// StateTransition is the StateTransition type specific to the supply verifier +// state machine. +type StateTransition = protofsm.StateTransition[Event, *Environment] + +// State is our sum-type ish interface that represents the current universe +// commitment verification state. +type State interface { + stateSealed() + IsTerminal() bool + ProcessEvent(Event, *Environment) (*StateTransition, error) + String() string +} + +// StateMachine is a state machine that handles verifying the on-chain supply +// commitment for a given asset. +type StateMachine = protofsm.StateMachine[Event, *Environment] + +// Config is a configuration struct that is used to initialize a new supply +// verifier state machine. +type Config = protofsm.StateMachineCfg[Event, *Environment] + +// FsmState is a type alias for the state of the supply verifier state machine. +type FsmState = protofsm.State[Event, *Environment] + +// FsmEvent is a type alias for the event type of the supply verifier state +// machine. +type FsmEvent = protofsm.EmittedEvent[Event] + +// StateSub is a type alias for the state subscriber of the supply verifier +// state machine. +type StateSub = protofsm.StateSubscriber[Event, *Environment] + +// InitEvent is the first event that is sent to the state machine. +type InitEvent struct{} + +// eventSealed is a special method that is used to seal the interface. +func (i *InitEvent) eventSealed() {} From e588fcf1f148bb7daa61b1c794774fe0aed0a0f2 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 8 Aug 2025 13:27:50 +0100 Subject: [PATCH 12/51] taprootassets: add supply verifier manager to main server config --- config.go | 6 ++++++ server.go | 12 ++++++++++++ tapcfg/server.go | 13 +++++++++++++ 3 files changed, 31 insertions(+) diff --git a/config.go b/config.go index 8ee49d78c..3c063d9c0 100644 --- a/config.go +++ b/config.go @@ -19,6 +19,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/signal" @@ -199,6 +200,11 @@ type Config struct { IgnoreChecker *tapdb.CachingIgnoreChecker + // SupplyVerifyManager is a service that is used to verify supply + // commitments for assets. Supply commitments are issuer published + // attestations of the total supply of an asset. + SupplyVerifyManager *supplyverifier.Manager + UniverseArchive *universe.Archive UniverseSyncer universe.Syncer diff --git a/server.go b/server.go index c4816286e..303df0c5e 100644 --- a/server.go +++ b/server.go @@ -217,6 +217,12 @@ func (s *Server) initialize(interceptorChain *rpcperms.InterceptorChain) error { err) } + // Start universe supply verify manager. + if err := s.cfg.SupplyVerifyManager.Start(); err != nil { + return fmt.Errorf("unable to start supply verify manager: %w", + err) + } + // Start the auxiliary components. if err := s.cfg.AuxLeafSigner.Start(); err != nil { return fmt.Errorf("unable to start aux leaf signer: %w", err) @@ -747,6 +753,12 @@ func (s *Server) Stop() error { err) } + // Stop universe supply verify manager. + if err := s.cfg.SupplyVerifyManager.Stop(); err != nil { + return fmt.Errorf("unable to stop supply verify manager: %w", + err) + } + if err := s.cfg.AuxLeafSigner.Stop(); err != nil { return err } diff --git a/tapcfg/server.go b/tapcfg/server.go index aa64d411c..4421c0790 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -25,6 +25,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapscript" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/clock" lfn "github.com/lightningnetwork/lnd/fn/v2" @@ -535,6 +536,17 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, }, ) + // Set up the supply verifier, which validates supply commitment leaves + // published by asset issuers. + supplyVerifyManager := supplyverifier.NewManager( + supplyverifier.ManagerCfg{ + Chain: chainBridge, + SupplyCommitView: supplyCommitStore, + IssuanceSubscriptions: universeSyncer, + DaemonAdapters: lndFsmDaemonAdapters, + }, + ) + // For the porter, we'll make a multi-notifier comprised of all the // possible proof file sources to ensure it can always fetch input // proofs. @@ -690,6 +702,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, FsmDaemonAdapters: lndFsmDaemonAdapters, SupplyCommitManager: supplyCommitManager, IgnoreChecker: ignoreChecker, + SupplyVerifyManager: supplyVerifyManager, UniverseArchive: uniArchive, UniverseSyncer: universeSyncer, UniverseFederation: universeFederation, From 0b4b0e9609630438f3e244d6ab354033c7defe94 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 17:47:56 +0100 Subject: [PATCH 13/51] universerpc: add InsertSupplyCommit RPC endpoint This endpoint allows the supply commit syncer to push new supply commits into a canonical universe. In this commit we also modify the REST path for UpdateSupplyCommit to `/v1/taproot-assets/universe/supply/update/{group_key_str}` from `/v1/taproot-assets/universe/supply/{group_key_str}`. --- fn/func.go | 23 + rpcserver.go | 326 +++++++ taprpc/perms.go | 7 + taprpc/universerpc/universe.pb.go | 1009 +++++++++++++++------- taprpc/universerpc/universe.pb.gw.go | 127 ++- taprpc/universerpc/universe.pb.json.go | 25 + taprpc/universerpc/universe.proto | 76 ++ taprpc/universerpc/universe.swagger.json | 148 +++- taprpc/universerpc/universe.yaml | 4 + taprpc/universerpc/universe_grpc.pb.go | 44 + 10 files changed, 1484 insertions(+), 305 deletions(-) diff --git a/fn/func.go b/fn/func.go index f054557ae..5feb7e3e8 100644 --- a/fn/func.go +++ b/fn/func.go @@ -93,6 +93,29 @@ func MapErr[I, O any, S []I](s S, f func(I) (O, error)) ([]O, error) { return output, nil } +// MapErrWithPtr applies the given fallible mapping function to each element of +// the given slice and generates a new slice. This is identical to MapErr, but +// can be used when the callback returns a pointer, and returns early if any +// single mapping fails. +func MapErrWithPtr[I, O any, S []I](s S, f func(I) (*O, error)) ([]O, error) { + output := make([]O, len(s)) + for i, x := range s { + outPtr, err := f(x) + if err != nil { + return nil, err + } + + if outPtr == nil { + return nil, fmt.Errorf("nil pointer returned for "+ + "item %d", i) + } + + output[i] = *outPtr + } + + return output, nil +} + // FlatMapErr applies the given mapping function to each element of the given // slice, concatenates the results into a new slice, and returns an error if // the mapping function fails. diff --git a/rpcserver.go b/rpcserver.go index 0fe135a7e..4aee100e2 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4559,6 +4559,332 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, }, nil } +// unmarshalMintSupplyLeaf converts an RPC SupplyLeafEntry into a NewMintEvent. +func unmarshalMintSupplyLeaf( + rpcLeaf *unirpc.SupplyLeafEntry) (*supplycommit.NewMintEvent, error) { + + if rpcLeaf == nil { + return nil, fmt.Errorf("supply leaf entry is nil") + } + + if rpcLeaf.LeafKey == nil { + return nil, fmt.Errorf("supply leaf key is nil") + } + + if rpcLeaf.LeafNode == nil { + return nil, fmt.Errorf("supply leaf node is nil") + } + + if len(rpcLeaf.RawLeaf) == 0 { + return nil, fmt.Errorf("missing RawLeaf data for mint event") + } + + var mintEvent supplycommit.NewMintEvent + err := mintEvent.Decode(bytes.NewReader(rpcLeaf.RawLeaf)) + if err != nil { + return nil, fmt.Errorf("unable to decode mint event: %w", err) + } + + // Validate that the decoded event matches the provided metadata. + if mintEvent.BlockHeight() != rpcLeaf.BlockHeight { + return nil, fmt.Errorf("block height mismatch: "+ + "decoded=%d, provided=%d", mintEvent.BlockHeight(), + rpcLeaf.BlockHeight) + } + + return &mintEvent, nil +} + +// unmarshalBurnSupplyLeaf converts an RPC SupplyLeafEntry into a NewBurnEvent. +func unmarshalBurnSupplyLeaf( + rpcLeaf *unirpc.SupplyLeafEntry) (*supplycommit.NewBurnEvent, error) { + + if rpcLeaf == nil { + return nil, fmt.Errorf("supply leaf entry is nil") + } + + if rpcLeaf.LeafKey == nil { + return nil, fmt.Errorf("supply leaf key is nil") + } + + if rpcLeaf.LeafNode == nil { + return nil, fmt.Errorf("supply leaf node is nil") + } + + if len(rpcLeaf.RawLeaf) == 0 { + return nil, fmt.Errorf("missing RawLeaf data for burn event") + } + + // Create and decode the burn leaf from raw leaf bytes. + var burnLeaf universe.BurnLeaf + err := burnLeaf.Decode(bytes.NewReader(rpcLeaf.RawLeaf)) + if err != nil { + return nil, fmt.Errorf("unable to decode burn leaf: %w", err) + } + + burnEvent := &supplycommit.NewBurnEvent{ + BurnLeaf: burnLeaf, + } + + // Validate that the decoded event matches the provided metadata. + if burnEvent.BlockHeight() != rpcLeaf.BlockHeight { + return nil, fmt.Errorf("block height mismatch: "+ + "decoded=%d, provided=%d", burnEvent.BlockHeight(), + rpcLeaf.BlockHeight) + } + + return burnEvent, nil +} + +// unmarshalIgnoreSupplyLeaf converts an RPC SupplyLeafEntry into a +// NewIgnoreEvent. +func unmarshalIgnoreSupplyLeaf( + rpcLeaf *unirpc.SupplyLeafEntry) (*supplycommit.NewIgnoreEvent, error) { + + if rpcLeaf == nil { + return nil, fmt.Errorf("supply leaf entry is nil") + } + + if rpcLeaf.LeafKey == nil { + return nil, fmt.Errorf("supply leaf key is nil") + } + + if rpcLeaf.LeafNode == nil { + return nil, fmt.Errorf("supply leaf node is nil") + } + + if len(rpcLeaf.RawLeaf) == 0 { + return nil, fmt.Errorf("missing RawLeaf data for ignore event") + } + + var signedIgnoreTuple universe.SignedIgnoreTuple + err := signedIgnoreTuple.Decode(bytes.NewReader(rpcLeaf.RawLeaf)) + if err != nil { + return nil, fmt.Errorf("unable to decode signed ignore "+ + "tuple: %w", err) + } + + ignoreEvent := &supplycommit.NewIgnoreEvent{ + SignedIgnoreTuple: signedIgnoreTuple, + } + + // Validate that the decoded event matches the provided metadata. + if ignoreEvent.BlockHeight() != rpcLeaf.BlockHeight { + return nil, fmt.Errorf("block height mismatch: "+ + "decoded=%d, provided=%d", ignoreEvent.BlockHeight(), + rpcLeaf.BlockHeight) + } + + return ignoreEvent, nil +} + +// unmarshalSupplyCommitChainData converts an RPC SupplyCommitChainData into +// both a supplycommit.RootCommitment and supplycommit.ChainProof. +func unmarshalSupplyCommitChainData( + rpcData *unirpc.SupplyCommitChainData) (*supplycommit.RootCommitment, + *supplycommit.ChainProof, error) { + + if rpcData == nil { + return nil, nil, fmt.Errorf("supply commit chain data is nil") + } + + var txn wire.MsgTx + err := txn.Deserialize(bytes.NewReader(rpcData.Txn)) + if err != nil { + return nil, nil, fmt.Errorf("unable to deserialize "+ + "transaction: %w", err) + } + + internalKey, err := btcec.ParsePubKey(rpcData.InternalKey) + if err != nil { + return nil, nil, fmt.Errorf("unable to parse internal key: %w", + err) + } + + outputKey, err := btcec.ParsePubKey(rpcData.OutputKey) + if err != nil { + return nil, nil, fmt.Errorf("unable to parse output key: %w", + err) + } + + // Convert supply root hash. + if len(rpcData.SupplyRootHash) != 32 { + return nil, nil, fmt.Errorf("invalid supply root hash size: "+ + "expected %d, got %d", 32, len(rpcData.SupplyRootHash)) + } + var supplyRootHash mssmt.NodeHash + copy(supplyRootHash[:], rpcData.SupplyRootHash) + + // Create commitment block from the hash. + var commitmentBlock fn.Option[supplycommit.CommitmentBlock] + if len(rpcData.BlockHash) > 0 { + if len(rpcData.BlockHash) != chainhash.HashSize { + return nil, nil, fmt.Errorf("invalid block hash size: "+ + "expected %d, got %d", chainhash.HashSize, + len(rpcData.BlockHash)) + } + var blockHash chainhash.Hash + copy(blockHash[:], rpcData.BlockHash) + + commitmentBlock = fn.Some(supplycommit.CommitmentBlock{ + Height: rpcData.BlockHeight, + Hash: blockHash, + TxIndex: rpcData.TxIndex, + }) + } + + rootCommitment := &supplycommit.RootCommitment{ + Txn: &txn, + TxOutIdx: rpcData.TxOutIdx, + InternalKey: keychain.KeyDescriptor{ + PubKey: internalKey, + }, + OutputKey: outputKey, + SupplyRoot: mssmt.NewComputedBranch( + supplyRootHash, rpcData.SupplyRootSum, + ), + CommitmentBlock: commitmentBlock, + } + + var blockHeader wire.BlockHeader + err = blockHeader.Deserialize(bytes.NewReader(rpcData.BlockHeader)) + if err != nil { + return nil, nil, fmt.Errorf("unable to deserialize block "+ + "header: %w", err) + } + + var merkleProof proof.TxMerkleProof + err = merkleProof.Decode(bytes.NewReader(rpcData.TxBlockMerkleProof)) + if err != nil { + return nil, nil, fmt.Errorf("unable to decode merkle proof: %w", + err) + } + + chainProof := &supplycommit.ChainProof{ + Header: blockHeader, + BlockHeight: rpcData.BlockHeight, + MerkleProof: merkleProof, + TxIndex: rpcData.TxIndex, + } + + return rootCommitment, chainProof, nil +} + +// InsertSupplyCommit stores a verified supply commitment for the given +// asset group in the node's local database. +func (r *rpcServer) InsertSupplyCommit(ctx context.Context, + req *unirpc.InsertSupplyCommitRequest) ( + *unirpc.InsertSupplyCommitResponse, error) { + + // Parse asset group key from the request. + var groupPubKey btcec.PublicKey + + switch { + case len(req.GetGroupKeyBytes()) > 0: + gk, err := btcec.ParsePubKey(req.GetGroupKeyBytes()) + if err != nil { + return nil, fmt.Errorf("parsing group key: %w", err) + } + + groupPubKey = *gk + + case len(req.GetGroupKeyStr()) > 0: + groupKeyBytes, err := hex.DecodeString(req.GetGroupKeyStr()) + if err != nil { + return nil, fmt.Errorf("decoding group key: %w", err) + } + + gk, err := btcec.ParsePubKey(groupKeyBytes) + if err != nil { + return nil, fmt.Errorf("parsing group key: %w", err) + } + + groupPubKey = *gk + + default: + return nil, fmt.Errorf("group key unspecified") + } + + // Log the operation for debugging purposes. + rpcsLog.Debugf("InsertSupplyCommitment called for group key: %x", + groupPubKey.SerializeCompressed()) + + // Unmarshal the supply commit chain data. + rootCommitment, chainProof, err := unmarshalSupplyCommitChainData( + req.ChainData, + ) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal chain data: %w", + err) + } + + // Initialize the SupplyLeaves structure to collect all unmarshalled + // events. + var supplyLeaves supplycommit.SupplyLeaves + + // Process issuance leaves. + supplyLeaves.IssuanceLeafEntries = make( + []supplycommit.NewMintEvent, 0, len(req.IssuanceLeaves), + ) + for _, rpcLeaf := range req.IssuanceLeaves { + mintEvent, err := unmarshalMintSupplyLeaf(rpcLeaf) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal issuance "+ + "leaf: %w", err) + } + supplyLeaves.IssuanceLeafEntries = append( + supplyLeaves.IssuanceLeafEntries, *mintEvent, + ) + } + + // Process burn leaves. + supplyLeaves.BurnLeafEntries = make( + []supplycommit.NewBurnEvent, 0, len(req.BurnLeaves), + ) + for _, rpcLeaf := range req.BurnLeaves { + burnEvent, err := unmarshalBurnSupplyLeaf(rpcLeaf) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal burn "+ + "leaf: %w", err) + } + supplyLeaves.BurnLeafEntries = append( + supplyLeaves.BurnLeafEntries, *burnEvent, + ) + } + + // Process ignore leaves. + supplyLeaves.IgnoreLeafEntries = make( + []supplycommit.NewIgnoreEvent, 0, len(req.IgnoreLeaves), + ) + for _, rpcLeaf := range req.IgnoreLeaves { + ignoreEvent, err := unmarshalIgnoreSupplyLeaf(rpcLeaf) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal ignore "+ + "leaf: %w", err) + } + supplyLeaves.IgnoreLeafEntries = append( + supplyLeaves.IgnoreLeafEntries, *ignoreEvent, + ) + } + + rpcsLog.Debugf("Successfully unmarshalled commitment, %d issuance, "+ + "%d burn, and %d ignore leaves, and chain proof", + len(supplyLeaves.IssuanceLeafEntries), + len(supplyLeaves.BurnLeafEntries), + len(supplyLeaves.IgnoreLeafEntries)) + + assetSpec := asset.NewSpecifierFromGroupKey(groupPubKey) + err = r.cfg.SupplyVerifyManager.InsertSupplyCommit( + ctx, assetSpec, *rootCommitment, supplyLeaves, *chainProof, + ) + if err != nil { + return nil, fmt.Errorf("failed to insert supply commitment: %w", + err) + } + + return &unirpc.InsertSupplyCommitResponse{}, nil +} + // SubscribeSendAssetEventNtfns registers a subscription to the event // notification stream which relates to the asset sending process. func (r *rpcServer) SubscribeSendAssetEventNtfns( diff --git a/taprpc/perms.go b/taprpc/perms.go index 2e87a3945..94135f48a 100644 --- a/taprpc/perms.go +++ b/taprpc/perms.go @@ -263,6 +263,10 @@ var ( Entity: "universe", Action: "write", }}, + "/universerpc.Universe/InsertSupplyCommit": {{ + Entity: "universe", + Action: "write", + }}, "/universerpc.Universe/FetchSupplyCommit": {{ Entity: "universe", Action: "read", @@ -371,12 +375,15 @@ func MacaroonWhitelist(allowUniPublicAccessRead bool, if allowUniPublicAccessRead || allowPublicUniProofCourier { whitelist["/universerpc.Universe/QueryProof"] = struct{}{} whitelist["/universerpc.Universe/FetchSupplyLeaves"] = struct{}{} + whitelist["/universerpc.Universe/FetchSupplyCommit"] = struct{}{} whitelist["/authmailboxrpc.Mailbox/ReceiveMessages"] = struct{}{} } // Conditionally whitelist universe server write methods. + // nolint: lll if allowUniPublicAccessWrite || allowPublicUniProofCourier { whitelist["/universerpc.Universe/InsertProof"] = struct{}{} + whitelist["/universerpc.Universe/InsertSupplyCommit"] = struct{}{} whitelist["/authmailboxrpc.Mailbox/SendMessage"] = struct{}{} } diff --git a/taprpc/universerpc/universe.pb.go b/taprpc/universerpc/universe.pb.go index 558cb2fb4..8a1d17538 100644 --- a/taprpc/universerpc/universe.pb.go +++ b/taprpc/universerpc/universe.pb.go @@ -4339,6 +4339,312 @@ func (x *FetchSupplyLeavesResponse) GetIgnoreLeaves() []*SupplyLeafEntry { return nil } +// SupplyCommitChainData represents the on-chain artifacts for a supply +// commitment update. +type SupplyCommitChainData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The raw transaction that created the root commitment. + Txn []byte `protobuf:"bytes,1,opt,name=txn,proto3" json:"txn,omitempty"` + // The index of the output in the transaction where the commitment resides. + TxOutIdx uint32 `protobuf:"varint,2,opt,name=tx_out_idx,json=txOutIdx,proto3" json:"tx_out_idx,omitempty"` + // The internal key used to create the commitment output. + InternalKey []byte `protobuf:"bytes,3,opt,name=internal_key,json=internalKey,proto3" json:"internal_key,omitempty"` + // The taproot output key used to create the commitment output. + OutputKey []byte `protobuf:"bytes,4,opt,name=output_key,json=outputKey,proto3" json:"output_key,omitempty"` + // The root hash of the supply tree that contains the set of + // sub-commitments. The sum value of this tree is the outstanding supply + // value. + SupplyRootHash []byte `protobuf:"bytes,5,opt,name=supply_root_hash,json=supplyRootHash,proto3" json:"supply_root_hash,omitempty"` + // The sum value of the supply root tree, representing the outstanding + // supply amount. + SupplyRootSum uint64 `protobuf:"varint,6,opt,name=supply_root_sum,json=supplyRootSum,proto3" json:"supply_root_sum,omitempty"` + // The block header of the block that contains the supply commitment + // transaction. + BlockHeader []byte `protobuf:"bytes,7,opt,name=block_header,json=blockHeader,proto3" json:"block_header,omitempty"` + // The hash of the block that contains the commitment. + BlockHash []byte `protobuf:"bytes,8,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + // The block height of the block that contains the supply commitment + // transaction. + BlockHeight uint32 `protobuf:"varint,9,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // The merkle proof that proves that the supply commitment transaction is + // included in the block. + TxBlockMerkleProof []byte `protobuf:"bytes,10,opt,name=tx_block_merkle_proof,json=txBlockMerkleProof,proto3" json:"tx_block_merkle_proof,omitempty"` + // The index of the supply commitment transaction in the block. + TxIndex uint32 `protobuf:"varint,11,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` +} + +func (x *SupplyCommitChainData) Reset() { + *x = SupplyCommitChainData{} + if protoimpl.UnsafeEnabled { + mi := &file_universerpc_universe_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SupplyCommitChainData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SupplyCommitChainData) ProtoMessage() {} + +func (x *SupplyCommitChainData) ProtoReflect() protoreflect.Message { + mi := &file_universerpc_universe_proto_msgTypes[62] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SupplyCommitChainData.ProtoReflect.Descriptor instead. +func (*SupplyCommitChainData) Descriptor() ([]byte, []int) { + return file_universerpc_universe_proto_rawDescGZIP(), []int{62} +} + +func (x *SupplyCommitChainData) GetTxn() []byte { + if x != nil { + return x.Txn + } + return nil +} + +func (x *SupplyCommitChainData) GetTxOutIdx() uint32 { + if x != nil { + return x.TxOutIdx + } + return 0 +} + +func (x *SupplyCommitChainData) GetInternalKey() []byte { + if x != nil { + return x.InternalKey + } + return nil +} + +func (x *SupplyCommitChainData) GetOutputKey() []byte { + if x != nil { + return x.OutputKey + } + return nil +} + +func (x *SupplyCommitChainData) GetSupplyRootHash() []byte { + if x != nil { + return x.SupplyRootHash + } + return nil +} + +func (x *SupplyCommitChainData) GetSupplyRootSum() uint64 { + if x != nil { + return x.SupplyRootSum + } + return 0 +} + +func (x *SupplyCommitChainData) GetBlockHeader() []byte { + if x != nil { + return x.BlockHeader + } + return nil +} + +func (x *SupplyCommitChainData) GetBlockHash() []byte { + if x != nil { + return x.BlockHash + } + return nil +} + +func (x *SupplyCommitChainData) GetBlockHeight() uint32 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *SupplyCommitChainData) GetTxBlockMerkleProof() []byte { + if x != nil { + return x.TxBlockMerkleProof + } + return nil +} + +func (x *SupplyCommitChainData) GetTxIndex() uint32 { + if x != nil { + return x.TxIndex + } + return 0 +} + +type InsertSupplyCommitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique identifier for the target asset group whose supply commitment + // is being inserted. + // + // Types that are assignable to GroupKey: + // + // *InsertSupplyCommitRequest_GroupKeyBytes + // *InsertSupplyCommitRequest_GroupKeyStr + GroupKey isInsertSupplyCommitRequest_GroupKey `protobuf_oneof:"group_key"` + // The supply commitment chain data that contains both the commitment and + // chain proof information. + ChainData *SupplyCommitChainData `protobuf:"bytes,3,opt,name=chain_data,json=chainData,proto3" json:"chain_data,omitempty"` + // The supply leaves that represent the supply changes for the asset group. + IssuanceLeaves []*SupplyLeafEntry `protobuf:"bytes,4,rep,name=issuance_leaves,json=issuanceLeaves,proto3" json:"issuance_leaves,omitempty"` + BurnLeaves []*SupplyLeafEntry `protobuf:"bytes,5,rep,name=burn_leaves,json=burnLeaves,proto3" json:"burn_leaves,omitempty"` + IgnoreLeaves []*SupplyLeafEntry `protobuf:"bytes,6,rep,name=ignore_leaves,json=ignoreLeaves,proto3" json:"ignore_leaves,omitempty"` +} + +func (x *InsertSupplyCommitRequest) Reset() { + *x = InsertSupplyCommitRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_universerpc_universe_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InsertSupplyCommitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InsertSupplyCommitRequest) ProtoMessage() {} + +func (x *InsertSupplyCommitRequest) ProtoReflect() protoreflect.Message { + mi := &file_universerpc_universe_proto_msgTypes[63] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InsertSupplyCommitRequest.ProtoReflect.Descriptor instead. +func (*InsertSupplyCommitRequest) Descriptor() ([]byte, []int) { + return file_universerpc_universe_proto_rawDescGZIP(), []int{63} +} + +func (m *InsertSupplyCommitRequest) GetGroupKey() isInsertSupplyCommitRequest_GroupKey { + if m != nil { + return m.GroupKey + } + return nil +} + +func (x *InsertSupplyCommitRequest) GetGroupKeyBytes() []byte { + if x, ok := x.GetGroupKey().(*InsertSupplyCommitRequest_GroupKeyBytes); ok { + return x.GroupKeyBytes + } + return nil +} + +func (x *InsertSupplyCommitRequest) GetGroupKeyStr() string { + if x, ok := x.GetGroupKey().(*InsertSupplyCommitRequest_GroupKeyStr); ok { + return x.GroupKeyStr + } + return "" +} + +func (x *InsertSupplyCommitRequest) GetChainData() *SupplyCommitChainData { + if x != nil { + return x.ChainData + } + return nil +} + +func (x *InsertSupplyCommitRequest) GetIssuanceLeaves() []*SupplyLeafEntry { + if x != nil { + return x.IssuanceLeaves + } + return nil +} + +func (x *InsertSupplyCommitRequest) GetBurnLeaves() []*SupplyLeafEntry { + if x != nil { + return x.BurnLeaves + } + return nil +} + +func (x *InsertSupplyCommitRequest) GetIgnoreLeaves() []*SupplyLeafEntry { + if x != nil { + return x.IgnoreLeaves + } + return nil +} + +type isInsertSupplyCommitRequest_GroupKey interface { + isInsertSupplyCommitRequest_GroupKey() +} + +type InsertSupplyCommitRequest_GroupKeyBytes struct { + // The 32-byte asset group key specified as raw bytes (gRPC only). + GroupKeyBytes []byte `protobuf:"bytes,1,opt,name=group_key_bytes,json=groupKeyBytes,proto3,oneof"` +} + +type InsertSupplyCommitRequest_GroupKeyStr struct { + // The 32-byte asset group key encoded as hex string (use this for + // REST). + GroupKeyStr string `protobuf:"bytes,2,opt,name=group_key_str,json=groupKeyStr,proto3,oneof"` +} + +func (*InsertSupplyCommitRequest_GroupKeyBytes) isInsertSupplyCommitRequest_GroupKey() {} + +func (*InsertSupplyCommitRequest_GroupKeyStr) isInsertSupplyCommitRequest_GroupKey() {} + +type InsertSupplyCommitResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *InsertSupplyCommitResponse) Reset() { + *x = InsertSupplyCommitResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_universerpc_universe_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InsertSupplyCommitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InsertSupplyCommitResponse) ProtoMessage() {} + +func (x *InsertSupplyCommitResponse) ProtoReflect() protoreflect.Message { + mi := &file_universerpc_universe_proto_msgTypes[64] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InsertSupplyCommitResponse.ProtoReflect.Descriptor instead. +func (*InsertSupplyCommitResponse) Descriptor() ([]byte, []int) { + return file_universerpc_universe_proto_rawDescGZIP(), []int{64} +} + var File_universerpc_universe_proto protoreflect.FileDescriptor var file_universerpc_universe_proto_rawDesc = []byte{ @@ -4911,173 +5217,231 @@ var file_universerpc_universe_proto_rawDesc = []byte{ 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, - 0x65, 0x73, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x50, - 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, - 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x02, 0x2a, 0x39, 0x0a, - 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x6f, 0x64, - 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, - 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x59, 0x4e, - 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, 0x01, 0x0a, 0x0e, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x0c, 0x53, - 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x16, 0x0a, - 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, - 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, - 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x53, - 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, - 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, - 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x50, 0x52, - 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, - 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x48, 0x45, 0x49, 0x47, 0x48, 0x54, - 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, - 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, 0x10, 0x07, 0x2a, 0x40, 0x0a, 0x0d, - 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, - 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, - 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x10, 0x01, 0x2a, 0x5f, - 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, - 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x54, - 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, - 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, - 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x32, - 0x8f, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, - 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x22, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, - 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, - 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, - 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, - 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, - 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x0f, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x44, 0x1a, 0x1e, 0x2e, + 0x65, 0x73, 0x22, 0x8e, 0x03, 0x0a, 0x15, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, + 0x74, 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, 0x78, 0x6e, 0x12, 0x1c, + 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x08, 0x74, 0x78, 0x4f, 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, 0x21, 0x0a, 0x0c, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, + 0x1d, 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x28, + 0x0a, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x52, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x53, 0x75, 0x6d, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x72, + 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x84, 0x03, 0x0a, 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, + 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, + 0x72, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, + 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, + 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, + 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, + 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, + 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, + 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, + 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, + 0x52, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, + 0x79, 0x6e, 0x63, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, + 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, + 0x0d, 0x0a, 0x09, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, + 0x01, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, + 0x74, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, + 0x45, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, + 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, + 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, + 0x02, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, + 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, + 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, + 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, + 0x54, 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, + 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, + 0x48, 0x45, 0x49, 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, + 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, + 0x10, 0x07, 0x2a, 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, + 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, + 0x53, 0x43, 0x10, 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, + 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, + 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, + 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, + 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, + 0x42, 0x4c, 0x45, 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, + 0x0a, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, + 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, - 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x18, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, - 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x1a, 0x1f, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x1d, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, - 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, - 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, - 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, - 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, - 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, - 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, + 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, + 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0d, 0x55, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x19, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, + 0x65, 0x73, 0x12, 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x44, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, + 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, + 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, + 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x46, 0x0a, 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, - 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, - 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x19, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x49, 0x67, 0x6e, 0x6f, - 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, - 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, - 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, - 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x25, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, - 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, - 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, - 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, - 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, - 0x72, 0x70, 0x63, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x2d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, + 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, + 0x0a, 0x13, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, + 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, + 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, + 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, + 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, + 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, + 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, + 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5093,7 +5457,7 @@ func file_universerpc_universe_proto_rawDescGZIP() []byte { } var file_universerpc_universe_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_universerpc_universe_proto_msgTypes = make([]protoimpl.MessageInfo, 64) +var file_universerpc_universe_proto_msgTypes = make([]protoimpl.MessageInfo, 67) var file_universerpc_universe_proto_goTypes = []any{ (ProofType)(0), // 0: universerpc.ProofType (UniverseSyncMode)(0), // 1: universerpc.UniverseSyncMode @@ -5162,140 +5526,149 @@ var file_universerpc_universe_proto_goTypes = []any{ (*SupplyLeafKey)(nil), // 64: universerpc.SupplyLeafKey (*SupplyLeafEntry)(nil), // 65: universerpc.SupplyLeafEntry (*FetchSupplyLeavesResponse)(nil), // 66: universerpc.FetchSupplyLeavesResponse - nil, // 67: universerpc.UniverseRoot.AmountsByAssetIdEntry - nil, // 68: universerpc.AssetRootResponse.UniverseRootsEntry - (*taprpc.Asset)(nil), // 69: taprpc.Asset - (*taprpc.AssetMeta)(nil), // 70: taprpc.AssetMeta - (*taprpc.GenesisReveal)(nil), // 71: taprpc.GenesisReveal - (*taprpc.GroupKeyReveal)(nil), // 72: taprpc.GroupKeyReveal - (taprpc.AssetType)(0), // 73: taprpc.AssetType - (*taprpc.AssetOutPoint)(nil), // 74: taprpc.AssetOutPoint + (*SupplyCommitChainData)(nil), // 67: universerpc.SupplyCommitChainData + (*InsertSupplyCommitRequest)(nil), // 68: universerpc.InsertSupplyCommitRequest + (*InsertSupplyCommitResponse)(nil), // 69: universerpc.InsertSupplyCommitResponse + nil, // 70: universerpc.UniverseRoot.AmountsByAssetIdEntry + nil, // 71: universerpc.AssetRootResponse.UniverseRootsEntry + (*taprpc.Asset)(nil), // 72: taprpc.Asset + (*taprpc.AssetMeta)(nil), // 73: taprpc.AssetMeta + (*taprpc.GenesisReveal)(nil), // 74: taprpc.GenesisReveal + (*taprpc.GroupKeyReveal)(nil), // 75: taprpc.GroupKeyReveal + (taprpc.AssetType)(0), // 76: taprpc.AssetType + (*taprpc.AssetOutPoint)(nil), // 77: taprpc.AssetOutPoint } var file_universerpc_universe_proto_depIdxs = []int32{ - 0, // 0: universerpc.MultiverseRootRequest.proof_type:type_name -> universerpc.ProofType - 9, // 1: universerpc.MultiverseRootRequest.specific_ids:type_name -> universerpc.ID - 8, // 2: universerpc.MultiverseRootResponse.multiverse_root:type_name -> universerpc.MerkleSumNode - 3, // 3: universerpc.AssetRootRequest.direction:type_name -> universerpc.SortDirection - 0, // 4: universerpc.ID.proof_type:type_name -> universerpc.ProofType - 9, // 5: universerpc.UniverseRoot.id:type_name -> universerpc.ID - 8, // 6: universerpc.UniverseRoot.mssmt_root:type_name -> universerpc.MerkleSumNode - 67, // 7: universerpc.UniverseRoot.amounts_by_asset_id:type_name -> universerpc.UniverseRoot.AmountsByAssetIdEntry - 68, // 8: universerpc.AssetRootResponse.universe_roots:type_name -> universerpc.AssetRootResponse.UniverseRootsEntry - 9, // 9: universerpc.AssetRootQuery.id:type_name -> universerpc.ID - 10, // 10: universerpc.QueryRootResponse.issuance_root:type_name -> universerpc.UniverseRoot - 10, // 11: universerpc.QueryRootResponse.transfer_root:type_name -> universerpc.UniverseRoot - 9, // 12: universerpc.DeleteRootQuery.id:type_name -> universerpc.ID - 16, // 13: universerpc.AssetKey.op:type_name -> universerpc.Outpoint - 9, // 14: universerpc.AssetLeafKeysRequest.id:type_name -> universerpc.ID - 3, // 15: universerpc.AssetLeafKeysRequest.direction:type_name -> universerpc.SortDirection - 17, // 16: universerpc.AssetLeafKeyResponse.asset_keys:type_name -> universerpc.AssetKey - 69, // 17: universerpc.AssetLeaf.asset:type_name -> taprpc.Asset - 20, // 18: universerpc.AssetLeafResponse.leaves:type_name -> universerpc.AssetLeaf - 9, // 19: universerpc.UniverseKey.id:type_name -> universerpc.ID - 17, // 20: universerpc.UniverseKey.leaf_key:type_name -> universerpc.AssetKey - 22, // 21: universerpc.AssetProofResponse.req:type_name -> universerpc.UniverseKey - 10, // 22: universerpc.AssetProofResponse.universe_root:type_name -> universerpc.UniverseRoot - 20, // 23: universerpc.AssetProofResponse.asset_leaf:type_name -> universerpc.AssetLeaf - 8, // 24: universerpc.AssetProofResponse.multiverse_root:type_name -> universerpc.MerkleSumNode - 24, // 25: universerpc.AssetProofResponse.issuance_data:type_name -> universerpc.IssuanceData - 70, // 26: universerpc.IssuanceData.meta_reveal:type_name -> taprpc.AssetMeta - 71, // 27: universerpc.IssuanceData.genesis_reveal:type_name -> taprpc.GenesisReveal - 72, // 28: universerpc.IssuanceData.group_key_reveal:type_name -> taprpc.GroupKeyReveal - 22, // 29: universerpc.AssetProof.key:type_name -> universerpc.UniverseKey - 20, // 30: universerpc.AssetProof.asset_leaf:type_name -> universerpc.AssetLeaf - 22, // 31: universerpc.PushProofRequest.key:type_name -> universerpc.UniverseKey - 35, // 32: universerpc.PushProofRequest.server:type_name -> universerpc.UniverseFederationServer - 22, // 33: universerpc.PushProofResponse.key:type_name -> universerpc.UniverseKey - 9, // 34: universerpc.SyncTarget.id:type_name -> universerpc.ID - 1, // 35: universerpc.SyncRequest.sync_mode:type_name -> universerpc.UniverseSyncMode - 30, // 36: universerpc.SyncRequest.sync_targets:type_name -> universerpc.SyncTarget - 10, // 37: universerpc.SyncedUniverse.old_asset_root:type_name -> universerpc.UniverseRoot - 10, // 38: universerpc.SyncedUniverse.new_asset_root:type_name -> universerpc.UniverseRoot - 20, // 39: universerpc.SyncedUniverse.new_asset_leaves:type_name -> universerpc.AssetLeaf - 32, // 40: universerpc.SyncResponse.synced_universes:type_name -> universerpc.SyncedUniverse - 35, // 41: universerpc.ListFederationServersResponse.servers:type_name -> universerpc.UniverseFederationServer - 35, // 42: universerpc.AddFederationServerRequest.servers:type_name -> universerpc.UniverseFederationServer - 35, // 43: universerpc.DeleteFederationServerRequest.servers:type_name -> universerpc.UniverseFederationServer - 4, // 44: universerpc.AssetStatsQuery.asset_type_filter:type_name -> universerpc.AssetTypeFilter - 2, // 45: universerpc.AssetStatsQuery.sort_by:type_name -> universerpc.AssetQuerySort - 3, // 46: universerpc.AssetStatsQuery.direction:type_name -> universerpc.SortDirection - 45, // 47: universerpc.AssetStatsSnapshot.group_anchor:type_name -> universerpc.AssetStatsAsset - 45, // 48: universerpc.AssetStatsSnapshot.asset:type_name -> universerpc.AssetStatsAsset - 73, // 49: universerpc.AssetStatsAsset.asset_type:type_name -> taprpc.AssetType - 44, // 50: universerpc.UniverseAssetStats.asset_stats:type_name -> universerpc.AssetStatsSnapshot - 49, // 51: universerpc.QueryEventsResponse.events:type_name -> universerpc.GroupedUniverseEvents - 52, // 52: universerpc.SetFederationSyncConfigRequest.global_sync_configs:type_name -> universerpc.GlobalFederationSyncConfig - 53, // 53: universerpc.SetFederationSyncConfigRequest.asset_sync_configs:type_name -> universerpc.AssetFederationSyncConfig - 0, // 54: universerpc.GlobalFederationSyncConfig.proof_type:type_name -> universerpc.ProofType - 9, // 55: universerpc.AssetFederationSyncConfig.id:type_name -> universerpc.ID - 9, // 56: universerpc.QueryFederationSyncConfigRequest.id:type_name -> universerpc.ID - 52, // 57: universerpc.QueryFederationSyncConfigResponse.global_sync_configs:type_name -> universerpc.GlobalFederationSyncConfig - 53, // 58: universerpc.QueryFederationSyncConfigResponse.asset_sync_configs:type_name -> universerpc.AssetFederationSyncConfig - 74, // 59: universerpc.IgnoreAssetOutPointRequest.asset_out_point:type_name -> taprpc.AssetOutPoint - 8, // 60: universerpc.IgnoreAssetOutPointResponse.leaf:type_name -> universerpc.MerkleSumNode - 8, // 61: universerpc.SupplyCommitSubtreeRoot.root_node:type_name -> universerpc.MerkleSumNode - 8, // 62: universerpc.FetchSupplyCommitResponse.supply_commitment_root:type_name -> universerpc.MerkleSumNode - 61, // 63: universerpc.FetchSupplyCommitResponse.issuance_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot - 61, // 64: universerpc.FetchSupplyCommitResponse.burn_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot - 61, // 65: universerpc.FetchSupplyCommitResponse.ignore_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot - 16, // 66: universerpc.SupplyLeafKey.outpoint:type_name -> universerpc.Outpoint - 64, // 67: universerpc.SupplyLeafEntry.leaf_key:type_name -> universerpc.SupplyLeafKey - 8, // 68: universerpc.SupplyLeafEntry.leaf_node:type_name -> universerpc.MerkleSumNode - 65, // 69: universerpc.FetchSupplyLeavesResponse.issuance_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 70: universerpc.FetchSupplyLeavesResponse.burn_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 71: universerpc.FetchSupplyLeavesResponse.ignore_leaves:type_name -> universerpc.SupplyLeafEntry - 10, // 72: universerpc.AssetRootResponse.UniverseRootsEntry.value:type_name -> universerpc.UniverseRoot - 5, // 73: universerpc.Universe.MultiverseRoot:input_type -> universerpc.MultiverseRootRequest - 7, // 74: universerpc.Universe.AssetRoots:input_type -> universerpc.AssetRootRequest - 12, // 75: universerpc.Universe.QueryAssetRoots:input_type -> universerpc.AssetRootQuery - 14, // 76: universerpc.Universe.DeleteAssetRoot:input_type -> universerpc.DeleteRootQuery - 18, // 77: universerpc.Universe.AssetLeafKeys:input_type -> universerpc.AssetLeafKeysRequest - 9, // 78: universerpc.Universe.AssetLeaves:input_type -> universerpc.ID - 22, // 79: universerpc.Universe.QueryProof:input_type -> universerpc.UniverseKey - 25, // 80: universerpc.Universe.InsertProof:input_type -> universerpc.AssetProof - 26, // 81: universerpc.Universe.PushProof:input_type -> universerpc.PushProofRequest - 28, // 82: universerpc.Universe.Info:input_type -> universerpc.InfoRequest - 31, // 83: universerpc.Universe.SyncUniverse:input_type -> universerpc.SyncRequest - 36, // 84: universerpc.Universe.ListFederationServers:input_type -> universerpc.ListFederationServersRequest - 38, // 85: universerpc.Universe.AddFederationServer:input_type -> universerpc.AddFederationServerRequest - 40, // 86: universerpc.Universe.DeleteFederationServer:input_type -> universerpc.DeleteFederationServerRequest - 33, // 87: universerpc.Universe.UniverseStats:input_type -> universerpc.StatsRequest - 43, // 88: universerpc.Universe.QueryAssetStats:input_type -> universerpc.AssetStatsQuery - 47, // 89: universerpc.Universe.QueryEvents:input_type -> universerpc.QueryEventsRequest - 50, // 90: universerpc.Universe.SetFederationSyncConfig:input_type -> universerpc.SetFederationSyncConfigRequest - 54, // 91: universerpc.Universe.QueryFederationSyncConfig:input_type -> universerpc.QueryFederationSyncConfigRequest - 56, // 92: universerpc.Universe.IgnoreAssetOutPoint:input_type -> universerpc.IgnoreAssetOutPointRequest - 58, // 93: universerpc.Universe.UpdateSupplyCommit:input_type -> universerpc.UpdateSupplyCommitRequest - 60, // 94: universerpc.Universe.FetchSupplyCommit:input_type -> universerpc.FetchSupplyCommitRequest - 63, // 95: universerpc.Universe.FetchSupplyLeaves:input_type -> universerpc.FetchSupplyLeavesRequest - 6, // 96: universerpc.Universe.MultiverseRoot:output_type -> universerpc.MultiverseRootResponse - 11, // 97: universerpc.Universe.AssetRoots:output_type -> universerpc.AssetRootResponse - 13, // 98: universerpc.Universe.QueryAssetRoots:output_type -> universerpc.QueryRootResponse - 15, // 99: universerpc.Universe.DeleteAssetRoot:output_type -> universerpc.DeleteRootResponse - 19, // 100: universerpc.Universe.AssetLeafKeys:output_type -> universerpc.AssetLeafKeyResponse - 21, // 101: universerpc.Universe.AssetLeaves:output_type -> universerpc.AssetLeafResponse - 23, // 102: universerpc.Universe.QueryProof:output_type -> universerpc.AssetProofResponse - 23, // 103: universerpc.Universe.InsertProof:output_type -> universerpc.AssetProofResponse - 27, // 104: universerpc.Universe.PushProof:output_type -> universerpc.PushProofResponse - 29, // 105: universerpc.Universe.Info:output_type -> universerpc.InfoResponse - 34, // 106: universerpc.Universe.SyncUniverse:output_type -> universerpc.SyncResponse - 37, // 107: universerpc.Universe.ListFederationServers:output_type -> universerpc.ListFederationServersResponse - 39, // 108: universerpc.Universe.AddFederationServer:output_type -> universerpc.AddFederationServerResponse - 41, // 109: universerpc.Universe.DeleteFederationServer:output_type -> universerpc.DeleteFederationServerResponse - 42, // 110: universerpc.Universe.UniverseStats:output_type -> universerpc.StatsResponse - 46, // 111: universerpc.Universe.QueryAssetStats:output_type -> universerpc.UniverseAssetStats - 48, // 112: universerpc.Universe.QueryEvents:output_type -> universerpc.QueryEventsResponse - 51, // 113: universerpc.Universe.SetFederationSyncConfig:output_type -> universerpc.SetFederationSyncConfigResponse - 55, // 114: universerpc.Universe.QueryFederationSyncConfig:output_type -> universerpc.QueryFederationSyncConfigResponse - 57, // 115: universerpc.Universe.IgnoreAssetOutPoint:output_type -> universerpc.IgnoreAssetOutPointResponse - 59, // 116: universerpc.Universe.UpdateSupplyCommit:output_type -> universerpc.UpdateSupplyCommitResponse - 62, // 117: universerpc.Universe.FetchSupplyCommit:output_type -> universerpc.FetchSupplyCommitResponse - 66, // 118: universerpc.Universe.FetchSupplyLeaves:output_type -> universerpc.FetchSupplyLeavesResponse - 96, // [96:119] is the sub-list for method output_type - 73, // [73:96] is the sub-list for method input_type - 73, // [73:73] is the sub-list for extension type_name - 73, // [73:73] is the sub-list for extension extendee - 0, // [0:73] is the sub-list for field type_name + 0, // 0: universerpc.MultiverseRootRequest.proof_type:type_name -> universerpc.ProofType + 9, // 1: universerpc.MultiverseRootRequest.specific_ids:type_name -> universerpc.ID + 8, // 2: universerpc.MultiverseRootResponse.multiverse_root:type_name -> universerpc.MerkleSumNode + 3, // 3: universerpc.AssetRootRequest.direction:type_name -> universerpc.SortDirection + 0, // 4: universerpc.ID.proof_type:type_name -> universerpc.ProofType + 9, // 5: universerpc.UniverseRoot.id:type_name -> universerpc.ID + 8, // 6: universerpc.UniverseRoot.mssmt_root:type_name -> universerpc.MerkleSumNode + 70, // 7: universerpc.UniverseRoot.amounts_by_asset_id:type_name -> universerpc.UniverseRoot.AmountsByAssetIdEntry + 71, // 8: universerpc.AssetRootResponse.universe_roots:type_name -> universerpc.AssetRootResponse.UniverseRootsEntry + 9, // 9: universerpc.AssetRootQuery.id:type_name -> universerpc.ID + 10, // 10: universerpc.QueryRootResponse.issuance_root:type_name -> universerpc.UniverseRoot + 10, // 11: universerpc.QueryRootResponse.transfer_root:type_name -> universerpc.UniverseRoot + 9, // 12: universerpc.DeleteRootQuery.id:type_name -> universerpc.ID + 16, // 13: universerpc.AssetKey.op:type_name -> universerpc.Outpoint + 9, // 14: universerpc.AssetLeafKeysRequest.id:type_name -> universerpc.ID + 3, // 15: universerpc.AssetLeafKeysRequest.direction:type_name -> universerpc.SortDirection + 17, // 16: universerpc.AssetLeafKeyResponse.asset_keys:type_name -> universerpc.AssetKey + 72, // 17: universerpc.AssetLeaf.asset:type_name -> taprpc.Asset + 20, // 18: universerpc.AssetLeafResponse.leaves:type_name -> universerpc.AssetLeaf + 9, // 19: universerpc.UniverseKey.id:type_name -> universerpc.ID + 17, // 20: universerpc.UniverseKey.leaf_key:type_name -> universerpc.AssetKey + 22, // 21: universerpc.AssetProofResponse.req:type_name -> universerpc.UniverseKey + 10, // 22: universerpc.AssetProofResponse.universe_root:type_name -> universerpc.UniverseRoot + 20, // 23: universerpc.AssetProofResponse.asset_leaf:type_name -> universerpc.AssetLeaf + 8, // 24: universerpc.AssetProofResponse.multiverse_root:type_name -> universerpc.MerkleSumNode + 24, // 25: universerpc.AssetProofResponse.issuance_data:type_name -> universerpc.IssuanceData + 73, // 26: universerpc.IssuanceData.meta_reveal:type_name -> taprpc.AssetMeta + 74, // 27: universerpc.IssuanceData.genesis_reveal:type_name -> taprpc.GenesisReveal + 75, // 28: universerpc.IssuanceData.group_key_reveal:type_name -> taprpc.GroupKeyReveal + 22, // 29: universerpc.AssetProof.key:type_name -> universerpc.UniverseKey + 20, // 30: universerpc.AssetProof.asset_leaf:type_name -> universerpc.AssetLeaf + 22, // 31: universerpc.PushProofRequest.key:type_name -> universerpc.UniverseKey + 35, // 32: universerpc.PushProofRequest.server:type_name -> universerpc.UniverseFederationServer + 22, // 33: universerpc.PushProofResponse.key:type_name -> universerpc.UniverseKey + 9, // 34: universerpc.SyncTarget.id:type_name -> universerpc.ID + 1, // 35: universerpc.SyncRequest.sync_mode:type_name -> universerpc.UniverseSyncMode + 30, // 36: universerpc.SyncRequest.sync_targets:type_name -> universerpc.SyncTarget + 10, // 37: universerpc.SyncedUniverse.old_asset_root:type_name -> universerpc.UniverseRoot + 10, // 38: universerpc.SyncedUniverse.new_asset_root:type_name -> universerpc.UniverseRoot + 20, // 39: universerpc.SyncedUniverse.new_asset_leaves:type_name -> universerpc.AssetLeaf + 32, // 40: universerpc.SyncResponse.synced_universes:type_name -> universerpc.SyncedUniverse + 35, // 41: universerpc.ListFederationServersResponse.servers:type_name -> universerpc.UniverseFederationServer + 35, // 42: universerpc.AddFederationServerRequest.servers:type_name -> universerpc.UniverseFederationServer + 35, // 43: universerpc.DeleteFederationServerRequest.servers:type_name -> universerpc.UniverseFederationServer + 4, // 44: universerpc.AssetStatsQuery.asset_type_filter:type_name -> universerpc.AssetTypeFilter + 2, // 45: universerpc.AssetStatsQuery.sort_by:type_name -> universerpc.AssetQuerySort + 3, // 46: universerpc.AssetStatsQuery.direction:type_name -> universerpc.SortDirection + 45, // 47: universerpc.AssetStatsSnapshot.group_anchor:type_name -> universerpc.AssetStatsAsset + 45, // 48: universerpc.AssetStatsSnapshot.asset:type_name -> universerpc.AssetStatsAsset + 76, // 49: universerpc.AssetStatsAsset.asset_type:type_name -> taprpc.AssetType + 44, // 50: universerpc.UniverseAssetStats.asset_stats:type_name -> universerpc.AssetStatsSnapshot + 49, // 51: universerpc.QueryEventsResponse.events:type_name -> universerpc.GroupedUniverseEvents + 52, // 52: universerpc.SetFederationSyncConfigRequest.global_sync_configs:type_name -> universerpc.GlobalFederationSyncConfig + 53, // 53: universerpc.SetFederationSyncConfigRequest.asset_sync_configs:type_name -> universerpc.AssetFederationSyncConfig + 0, // 54: universerpc.GlobalFederationSyncConfig.proof_type:type_name -> universerpc.ProofType + 9, // 55: universerpc.AssetFederationSyncConfig.id:type_name -> universerpc.ID + 9, // 56: universerpc.QueryFederationSyncConfigRequest.id:type_name -> universerpc.ID + 52, // 57: universerpc.QueryFederationSyncConfigResponse.global_sync_configs:type_name -> universerpc.GlobalFederationSyncConfig + 53, // 58: universerpc.QueryFederationSyncConfigResponse.asset_sync_configs:type_name -> universerpc.AssetFederationSyncConfig + 77, // 59: universerpc.IgnoreAssetOutPointRequest.asset_out_point:type_name -> taprpc.AssetOutPoint + 8, // 60: universerpc.IgnoreAssetOutPointResponse.leaf:type_name -> universerpc.MerkleSumNode + 8, // 61: universerpc.SupplyCommitSubtreeRoot.root_node:type_name -> universerpc.MerkleSumNode + 8, // 62: universerpc.FetchSupplyCommitResponse.supply_commitment_root:type_name -> universerpc.MerkleSumNode + 61, // 63: universerpc.FetchSupplyCommitResponse.issuance_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot + 61, // 64: universerpc.FetchSupplyCommitResponse.burn_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot + 61, // 65: universerpc.FetchSupplyCommitResponse.ignore_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot + 16, // 66: universerpc.SupplyLeafKey.outpoint:type_name -> universerpc.Outpoint + 64, // 67: universerpc.SupplyLeafEntry.leaf_key:type_name -> universerpc.SupplyLeafKey + 8, // 68: universerpc.SupplyLeafEntry.leaf_node:type_name -> universerpc.MerkleSumNode + 65, // 69: universerpc.FetchSupplyLeavesResponse.issuance_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 70: universerpc.FetchSupplyLeavesResponse.burn_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 71: universerpc.FetchSupplyLeavesResponse.ignore_leaves:type_name -> universerpc.SupplyLeafEntry + 67, // 72: universerpc.InsertSupplyCommitRequest.chain_data:type_name -> universerpc.SupplyCommitChainData + 65, // 73: universerpc.InsertSupplyCommitRequest.issuance_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 74: universerpc.InsertSupplyCommitRequest.burn_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 75: universerpc.InsertSupplyCommitRequest.ignore_leaves:type_name -> universerpc.SupplyLeafEntry + 10, // 76: universerpc.AssetRootResponse.UniverseRootsEntry.value:type_name -> universerpc.UniverseRoot + 5, // 77: universerpc.Universe.MultiverseRoot:input_type -> universerpc.MultiverseRootRequest + 7, // 78: universerpc.Universe.AssetRoots:input_type -> universerpc.AssetRootRequest + 12, // 79: universerpc.Universe.QueryAssetRoots:input_type -> universerpc.AssetRootQuery + 14, // 80: universerpc.Universe.DeleteAssetRoot:input_type -> universerpc.DeleteRootQuery + 18, // 81: universerpc.Universe.AssetLeafKeys:input_type -> universerpc.AssetLeafKeysRequest + 9, // 82: universerpc.Universe.AssetLeaves:input_type -> universerpc.ID + 22, // 83: universerpc.Universe.QueryProof:input_type -> universerpc.UniverseKey + 25, // 84: universerpc.Universe.InsertProof:input_type -> universerpc.AssetProof + 26, // 85: universerpc.Universe.PushProof:input_type -> universerpc.PushProofRequest + 28, // 86: universerpc.Universe.Info:input_type -> universerpc.InfoRequest + 31, // 87: universerpc.Universe.SyncUniverse:input_type -> universerpc.SyncRequest + 36, // 88: universerpc.Universe.ListFederationServers:input_type -> universerpc.ListFederationServersRequest + 38, // 89: universerpc.Universe.AddFederationServer:input_type -> universerpc.AddFederationServerRequest + 40, // 90: universerpc.Universe.DeleteFederationServer:input_type -> universerpc.DeleteFederationServerRequest + 33, // 91: universerpc.Universe.UniverseStats:input_type -> universerpc.StatsRequest + 43, // 92: universerpc.Universe.QueryAssetStats:input_type -> universerpc.AssetStatsQuery + 47, // 93: universerpc.Universe.QueryEvents:input_type -> universerpc.QueryEventsRequest + 50, // 94: universerpc.Universe.SetFederationSyncConfig:input_type -> universerpc.SetFederationSyncConfigRequest + 54, // 95: universerpc.Universe.QueryFederationSyncConfig:input_type -> universerpc.QueryFederationSyncConfigRequest + 56, // 96: universerpc.Universe.IgnoreAssetOutPoint:input_type -> universerpc.IgnoreAssetOutPointRequest + 58, // 97: universerpc.Universe.UpdateSupplyCommit:input_type -> universerpc.UpdateSupplyCommitRequest + 60, // 98: universerpc.Universe.FetchSupplyCommit:input_type -> universerpc.FetchSupplyCommitRequest + 63, // 99: universerpc.Universe.FetchSupplyLeaves:input_type -> universerpc.FetchSupplyLeavesRequest + 68, // 100: universerpc.Universe.InsertSupplyCommit:input_type -> universerpc.InsertSupplyCommitRequest + 6, // 101: universerpc.Universe.MultiverseRoot:output_type -> universerpc.MultiverseRootResponse + 11, // 102: universerpc.Universe.AssetRoots:output_type -> universerpc.AssetRootResponse + 13, // 103: universerpc.Universe.QueryAssetRoots:output_type -> universerpc.QueryRootResponse + 15, // 104: universerpc.Universe.DeleteAssetRoot:output_type -> universerpc.DeleteRootResponse + 19, // 105: universerpc.Universe.AssetLeafKeys:output_type -> universerpc.AssetLeafKeyResponse + 21, // 106: universerpc.Universe.AssetLeaves:output_type -> universerpc.AssetLeafResponse + 23, // 107: universerpc.Universe.QueryProof:output_type -> universerpc.AssetProofResponse + 23, // 108: universerpc.Universe.InsertProof:output_type -> universerpc.AssetProofResponse + 27, // 109: universerpc.Universe.PushProof:output_type -> universerpc.PushProofResponse + 29, // 110: universerpc.Universe.Info:output_type -> universerpc.InfoResponse + 34, // 111: universerpc.Universe.SyncUniverse:output_type -> universerpc.SyncResponse + 37, // 112: universerpc.Universe.ListFederationServers:output_type -> universerpc.ListFederationServersResponse + 39, // 113: universerpc.Universe.AddFederationServer:output_type -> universerpc.AddFederationServerResponse + 41, // 114: universerpc.Universe.DeleteFederationServer:output_type -> universerpc.DeleteFederationServerResponse + 42, // 115: universerpc.Universe.UniverseStats:output_type -> universerpc.StatsResponse + 46, // 116: universerpc.Universe.QueryAssetStats:output_type -> universerpc.UniverseAssetStats + 48, // 117: universerpc.Universe.QueryEvents:output_type -> universerpc.QueryEventsResponse + 51, // 118: universerpc.Universe.SetFederationSyncConfig:output_type -> universerpc.SetFederationSyncConfigResponse + 55, // 119: universerpc.Universe.QueryFederationSyncConfig:output_type -> universerpc.QueryFederationSyncConfigResponse + 57, // 120: universerpc.Universe.IgnoreAssetOutPoint:output_type -> universerpc.IgnoreAssetOutPointResponse + 59, // 121: universerpc.Universe.UpdateSupplyCommit:output_type -> universerpc.UpdateSupplyCommitResponse + 62, // 122: universerpc.Universe.FetchSupplyCommit:output_type -> universerpc.FetchSupplyCommitResponse + 66, // 123: universerpc.Universe.FetchSupplyLeaves:output_type -> universerpc.FetchSupplyLeavesResponse + 69, // 124: universerpc.Universe.InsertSupplyCommit:output_type -> universerpc.InsertSupplyCommitResponse + 101, // [101:125] is the sub-list for method output_type + 77, // [77:101] is the sub-list for method input_type + 77, // [77:77] is the sub-list for extension type_name + 77, // [77:77] is the sub-list for extension extendee + 0, // [0:77] is the sub-list for field type_name } func init() { file_universerpc_universe_proto_init() } @@ -6048,6 +6421,42 @@ func file_universerpc_universe_proto_init() { return nil } } + file_universerpc_universe_proto_msgTypes[62].Exporter = func(v any, i int) any { + switch v := v.(*SupplyCommitChainData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_universerpc_universe_proto_msgTypes[63].Exporter = func(v any, i int) any { + switch v := v.(*InsertSupplyCommitRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_universerpc_universe_proto_msgTypes[64].Exporter = func(v any, i int) any { + switch v := v.(*InsertSupplyCommitResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_universerpc_universe_proto_msgTypes[4].OneofWrappers = []any{ (*ID_AssetId)(nil), @@ -6073,13 +6482,17 @@ func file_universerpc_universe_proto_init() { (*FetchSupplyLeavesRequest_GroupKeyBytes)(nil), (*FetchSupplyLeavesRequest_GroupKeyStr)(nil), } + file_universerpc_universe_proto_msgTypes[63].OneofWrappers = []any{ + (*InsertSupplyCommitRequest_GroupKeyBytes)(nil), + (*InsertSupplyCommitRequest_GroupKeyStr)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_universerpc_universe_proto_rawDesc, NumEnums: 5, - NumMessages: 64, + NumMessages: 67, NumExtensions: 0, NumServices: 1, }, diff --git a/taprpc/universerpc/universe.pb.gw.go b/taprpc/universerpc/universe.pb.gw.go index a31d58d95..c0cdfbe49 100644 --- a/taprpc/universerpc/universe.pb.gw.go +++ b/taprpc/universerpc/universe.pb.gw.go @@ -1841,6 +1841,76 @@ func local_request_Universe_FetchSupplyLeaves_0(ctx context.Context, marshaler r } +func request_Universe_InsertSupplyCommit_0(ctx context.Context, marshaler runtime.Marshaler, client UniverseClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq InsertSupplyCommitRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_key_str"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_key_str") + } + + if protoReq.GroupKey == nil { + protoReq.GroupKey = &InsertSupplyCommitRequest_GroupKeyStr{} + } else if _, ok := protoReq.GroupKey.(*InsertSupplyCommitRequest_GroupKeyStr); !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "expect type: *InsertSupplyCommitRequest_GroupKeyStr, but: %t\n", protoReq.GroupKey) + } + protoReq.GroupKey.(*InsertSupplyCommitRequest_GroupKeyStr).GroupKeyStr, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_key_str", err) + } + + msg, err := client.InsertSupplyCommit(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Universe_InsertSupplyCommit_0(ctx context.Context, marshaler runtime.Marshaler, server UniverseServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq InsertSupplyCommitRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_key_str"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_key_str") + } + + if protoReq.GroupKey == nil { + protoReq.GroupKey = &InsertSupplyCommitRequest_GroupKeyStr{} + } else if _, ok := protoReq.GroupKey.(*InsertSupplyCommitRequest_GroupKeyStr); !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "expect type: *InsertSupplyCommitRequest_GroupKeyStr, but: %t\n", protoReq.GroupKey) + } + protoReq.GroupKey.(*InsertSupplyCommitRequest_GroupKeyStr).GroupKeyStr, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_key_str", err) + } + + msg, err := server.InsertSupplyCommit(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterUniverseHandlerServer registers the http handlers for service Universe to "mux". // UnaryRPC :call UniverseServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -2505,7 +2575,7 @@ func RegisterUniverseHandlerServer(ctx context.Context, mux *runtime.ServeMux, s inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/universerpc.Universe/UpdateSupplyCommit", runtime.WithHTTPPathPattern("/v1/taproot-assets/universe/supply/{group_key_str}")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/universerpc.Universe/UpdateSupplyCommit", runtime.WithHTTPPathPattern("/v1/taproot-assets/universe/supply/update/{group_key_str}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return @@ -2572,6 +2642,31 @@ func RegisterUniverseHandlerServer(ctx context.Context, mux *runtime.ServeMux, s }) + mux.Handle("POST", pattern_Universe_InsertSupplyCommit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/universerpc.Universe/InsertSupplyCommit", runtime.WithHTTPPathPattern("/v1/taproot-assets/universe/supply/{group_key_str}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Universe_InsertSupplyCommit_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Universe_InsertSupplyCommit_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -3191,7 +3286,7 @@ func RegisterUniverseHandlerClient(ctx context.Context, mux *runtime.ServeMux, c inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/universerpc.Universe/UpdateSupplyCommit", runtime.WithHTTPPathPattern("/v1/taproot-assets/universe/supply/{group_key_str}")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/universerpc.Universe/UpdateSupplyCommit", runtime.WithHTTPPathPattern("/v1/taproot-assets/universe/supply/update/{group_key_str}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return @@ -3251,6 +3346,28 @@ func RegisterUniverseHandlerClient(ctx context.Context, mux *runtime.ServeMux, c }) + mux.Handle("POST", pattern_Universe_InsertSupplyCommit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/universerpc.Universe/InsertSupplyCommit", runtime.WithHTTPPathPattern("/v1/taproot-assets/universe/supply/{group_key_str}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Universe_InsertSupplyCommit_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Universe_InsertSupplyCommit_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -3307,11 +3424,13 @@ var ( pattern_Universe_IgnoreAssetOutPoint_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"v1", "taproot-assets", "universe", "supply", "ignore"}, "")) - pattern_Universe_UpdateSupplyCommit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "taproot-assets", "universe", "supply", "group_key_str"}, "")) + pattern_Universe_UpdateSupplyCommit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"v1", "taproot-assets", "universe", "supply", "update", "group_key_str"}, "")) pattern_Universe_FetchSupplyCommit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "taproot-assets", "universe", "supply", "group_key_str"}, "")) pattern_Universe_FetchSupplyLeaves_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"v1", "taproot-assets", "universe", "supply", "leaves", "group_key_str"}, "")) + + pattern_Universe_InsertSupplyCommit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "taproot-assets", "universe", "supply", "group_key_str"}, "")) ) var ( @@ -3372,4 +3491,6 @@ var ( forward_Universe_FetchSupplyCommit_0 = runtime.ForwardResponseMessage forward_Universe_FetchSupplyLeaves_0 = runtime.ForwardResponseMessage + + forward_Universe_InsertSupplyCommit_0 = runtime.ForwardResponseMessage ) diff --git a/taprpc/universerpc/universe.pb.json.go b/taprpc/universerpc/universe.pb.json.go index ceae8733e..e4f762173 100644 --- a/taprpc/universerpc/universe.pb.json.go +++ b/taprpc/universerpc/universe.pb.json.go @@ -595,4 +595,29 @@ func RegisterUniverseJSONCallbacks(registry map[string]func(ctx context.Context, } callback(string(respBytes), nil) } + + registry["universerpc.Universe.InsertSupplyCommit"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &InsertSupplyCommitRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewUniverseClient(conn) + resp, err := client.InsertSupplyCommit(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } } diff --git a/taprpc/universerpc/universe.proto b/taprpc/universerpc/universe.proto index 3cb8103e9..cf47fca3b 100644 --- a/taprpc/universerpc/universe.proto +++ b/taprpc/universerpc/universe.proto @@ -187,6 +187,14 @@ service Universe { */ rpc FetchSupplyLeaves (FetchSupplyLeavesRequest) returns (FetchSupplyLeavesResponse); + + /* tapcli: `universe supplycommit insert` + InsertSupplyCommit inserts a supply commitment for a specific asset + group. This includes the commitment details, supply leaves (issuance, burn, + and ignore), and chain proof that proves the commitment has been mined. + */ + rpc InsertSupplyCommit (InsertSupplyCommitRequest) + returns (InsertSupplyCommitResponse); } message MultiverseRootRequest { @@ -931,4 +939,72 @@ message FetchSupplyLeavesResponse { repeated SupplyLeafEntry issuance_leaves = 1; repeated SupplyLeafEntry burn_leaves = 2; repeated SupplyLeafEntry ignore_leaves = 3; +} + +// SupplyCommitChainData represents the on-chain artifacts for a supply +// commitment update. +message SupplyCommitChainData { + // The raw transaction that created the root commitment. + bytes txn = 1; + + // The index of the output in the transaction where the commitment resides. + uint32 tx_out_idx = 2; + + // The internal key used to create the commitment output. + bytes internal_key = 3; + + // The taproot output key used to create the commitment output. + bytes output_key = 4; + + // The root hash of the supply tree that contains the set of + // sub-commitments. The sum value of this tree is the outstanding supply + // value. + bytes supply_root_hash = 5; + + // The sum value of the supply root tree, representing the outstanding + // supply amount. + uint64 supply_root_sum = 6; + + // The block header of the block that contains the supply commitment + // transaction. + bytes block_header = 7; + + // The hash of the block that contains the commitment. + bytes block_hash = 8; + + // The block height of the block that contains the supply commitment + // transaction. + uint32 block_height = 9; + + // The merkle proof that proves that the supply commitment transaction is + // included in the block. + bytes tx_block_merkle_proof = 10; + + // The index of the supply commitment transaction in the block. + uint32 tx_index = 11; +} + +message InsertSupplyCommitRequest { + // The unique identifier for the target asset group whose supply commitment + // is being inserted. + oneof group_key { + // The 32-byte asset group key specified as raw bytes (gRPC only). + bytes group_key_bytes = 1; + + // The 32-byte asset group key encoded as hex string (use this for + // REST). + string group_key_str = 2; + } + + // The supply commitment chain data that contains both the commitment and + // chain proof information. + SupplyCommitChainData chain_data = 3; + + // The supply leaves that represent the supply changes for the asset group. + repeated SupplyLeafEntry issuance_leaves = 4; + repeated SupplyLeafEntry burn_leaves = 5; + repeated SupplyLeafEntry ignore_leaves = 6; +} + +message InsertSupplyCommitResponse { } \ No newline at end of file diff --git a/taprpc/universerpc/universe.swagger.json b/taprpc/universerpc/universe.swagger.json index ddb0a2b17..4bf5d5994 100644 --- a/taprpc/universerpc/universe.swagger.json +++ b/taprpc/universerpc/universe.swagger.json @@ -1430,6 +1430,46 @@ ] } }, + "/v1/taproot-assets/universe/supply/update/{group_key_str}": { + "post": { + "summary": "tapcli: `universe updatesupplycommit`\nUpdateSupplyCommit updates the on-chain supply commitment for a specific\nasset group.", + "operationId": "Universe_UpdateSupplyCommit", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/universerpcUpdateSupplyCommitResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "group_key_str", + "description": "The 32-byte asset group key encoded as hex string (use this for\nREST).", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UniverseUpdateSupplyCommitBody" + } + } + ], + "tags": [ + "Universe" + ] + } + }, "/v1/taproot-assets/universe/supply/{group_key_str}": { "get": { "summary": "tapcli: `universe fetchsupplycommit`\nFetchSupplyCommit fetches the on-chain supply commitment for a specific\nasset group.", @@ -1506,13 +1546,13 @@ ] }, "post": { - "summary": "tapcli: `universe updatesupplycommit`\nUpdateSupplyCommit updates the on-chain supply commitment for a specific\nasset group.", - "operationId": "Universe_UpdateSupplyCommit", + "summary": "tapcli: `universe supplycommit insert`\nInsertSupplyCommit inserts a supply commitment for a specific asset\ngroup. This includes the commitment details, supply leaves (issuance, burn,\nand ignore), and chain proof that proves the commitment has been mined.", + "operationId": "Universe_InsertSupplyCommit", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/universerpcUpdateSupplyCommitResponse" + "$ref": "#/definitions/universerpcInsertSupplyCommitResponse" } }, "default": { @@ -1535,7 +1575,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/UniverseUpdateSupplyCommitBody" + "$ref": "#/definitions/UniverseInsertSupplyCommitBody" } } ], @@ -1690,6 +1730,42 @@ } } }, + "UniverseInsertSupplyCommitBody": { + "type": "object", + "properties": { + "group_key_bytes": { + "type": "string", + "format": "byte", + "description": "The 32-byte asset group key specified as raw bytes (gRPC only)." + }, + "chain_data": { + "$ref": "#/definitions/universerpcSupplyCommitChainData", + "description": "The supply commitment chain data that contains both the commitment and\nchain proof information." + }, + "issuance_leaves": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/universerpcSupplyLeafEntry" + }, + "description": "The supply leaves that represent the supply changes for the asset group." + }, + "burn_leaves": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/universerpcSupplyLeafEntry" + } + }, + "ignore_leaves": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/universerpcSupplyLeafEntry" + } + } + } + }, "UniversePushProofBody": { "type": "object", "properties": { @@ -2598,6 +2674,9 @@ } } }, + "universerpcInsertSupplyCommitResponse": { + "type": "object" + }, "universerpcIssuanceData": { "type": "object", "properties": { @@ -2801,6 +2880,67 @@ } } }, + "universerpcSupplyCommitChainData": { + "type": "object", + "properties": { + "txn": { + "type": "string", + "format": "byte", + "description": "The raw transaction that created the root commitment." + }, + "tx_out_idx": { + "type": "integer", + "format": "int64", + "description": "The index of the output in the transaction where the commitment resides." + }, + "internal_key": { + "type": "string", + "format": "byte", + "description": "The internal key used to create the commitment output." + }, + "output_key": { + "type": "string", + "format": "byte", + "description": "The taproot output key used to create the commitment output." + }, + "supply_root_hash": { + "type": "string", + "format": "byte", + "description": "The root hash of the supply tree that contains the set of\nsub-commitments. The sum value of this tree is the outstanding supply\nvalue." + }, + "supply_root_sum": { + "type": "string", + "format": "uint64", + "description": "The sum value of the supply root tree, representing the outstanding\nsupply amount." + }, + "block_header": { + "type": "string", + "format": "byte", + "description": "The block header of the block that contains the supply commitment\ntransaction." + }, + "block_hash": { + "type": "string", + "format": "byte", + "description": "The hash of the block that contains the commitment." + }, + "block_height": { + "type": "integer", + "format": "int64", + "description": "The block height of the block that contains the supply commitment\ntransaction." + }, + "tx_block_merkle_proof": { + "type": "string", + "format": "byte", + "description": "The merkle proof that proves that the supply commitment transaction is\nincluded in the block." + }, + "tx_index": { + "type": "integer", + "format": "int64", + "description": "The index of the supply commitment transaction in the block." + } + }, + "description": "SupplyCommitChainData represents the on-chain artifacts for a supply\ncommitment update." + }, "universerpcSupplyCommitSubtreeRoot": { "type": "object", "properties": { diff --git a/taprpc/universerpc/universe.yaml b/taprpc/universerpc/universe.yaml index 2edbefb44..e7518f5f2 100644 --- a/taprpc/universerpc/universe.yaml +++ b/taprpc/universerpc/universe.yaml @@ -85,6 +85,10 @@ http: body: "*" - selector: universerpc.Universe.UpdateSupplyCommit + post: "/v1/taproot-assets/universe/supply/update/{group_key_str}" + body: "*" + + - selector: universerpc.Universe.InsertSupplyCommit post: "/v1/taproot-assets/universe/supply/{group_key_str}" body: "*" diff --git a/taprpc/universerpc/universe_grpc.pb.go b/taprpc/universerpc/universe_grpc.pb.go index 46970afcb..995b10ca4 100644 --- a/taprpc/universerpc/universe_grpc.pb.go +++ b/taprpc/universerpc/universe_grpc.pb.go @@ -135,6 +135,11 @@ type UniverseClient interface { // within a specified block height range. The leaves include issuance, burn, // and ignore leaves, which represent the supply changes for the asset group. FetchSupplyLeaves(ctx context.Context, in *FetchSupplyLeavesRequest, opts ...grpc.CallOption) (*FetchSupplyLeavesResponse, error) + // tapcli: `universe supplycommit insert` + // InsertSupplyCommit inserts a supply commitment for a specific asset + // group. This includes the commitment details, supply leaves (issuance, burn, + // and ignore), and chain proof that proves the commitment has been mined. + InsertSupplyCommit(ctx context.Context, in *InsertSupplyCommitRequest, opts ...grpc.CallOption) (*InsertSupplyCommitResponse, error) } type universeClient struct { @@ -352,6 +357,15 @@ func (c *universeClient) FetchSupplyLeaves(ctx context.Context, in *FetchSupplyL return out, nil } +func (c *universeClient) InsertSupplyCommit(ctx context.Context, in *InsertSupplyCommitRequest, opts ...grpc.CallOption) (*InsertSupplyCommitResponse, error) { + out := new(InsertSupplyCommitResponse) + err := c.cc.Invoke(ctx, "/universerpc.Universe/InsertSupplyCommit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // UniverseServer is the server API for Universe service. // All implementations must embed UnimplementedUniverseServer // for forward compatibility @@ -473,6 +487,11 @@ type UniverseServer interface { // within a specified block height range. The leaves include issuance, burn, // and ignore leaves, which represent the supply changes for the asset group. FetchSupplyLeaves(context.Context, *FetchSupplyLeavesRequest) (*FetchSupplyLeavesResponse, error) + // tapcli: `universe supplycommit insert` + // InsertSupplyCommit inserts a supply commitment for a specific asset + // group. This includes the commitment details, supply leaves (issuance, burn, + // and ignore), and chain proof that proves the commitment has been mined. + InsertSupplyCommit(context.Context, *InsertSupplyCommitRequest) (*InsertSupplyCommitResponse, error) mustEmbedUnimplementedUniverseServer() } @@ -549,6 +568,9 @@ func (UnimplementedUniverseServer) FetchSupplyCommit(context.Context, *FetchSupp func (UnimplementedUniverseServer) FetchSupplyLeaves(context.Context, *FetchSupplyLeavesRequest) (*FetchSupplyLeavesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FetchSupplyLeaves not implemented") } +func (UnimplementedUniverseServer) InsertSupplyCommit(context.Context, *InsertSupplyCommitRequest) (*InsertSupplyCommitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method InsertSupplyCommit not implemented") +} func (UnimplementedUniverseServer) mustEmbedUnimplementedUniverseServer() {} // UnsafeUniverseServer may be embedded to opt out of forward compatibility for this service. @@ -976,6 +998,24 @@ func _Universe_FetchSupplyLeaves_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _Universe_InsertSupplyCommit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InsertSupplyCommitRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UniverseServer).InsertSupplyCommit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/universerpc.Universe/InsertSupplyCommit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UniverseServer).InsertSupplyCommit(ctx, req.(*InsertSupplyCommitRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Universe_ServiceDesc is the grpc.ServiceDesc for Universe service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1075,6 +1115,10 @@ var Universe_ServiceDesc = grpc.ServiceDesc{ MethodName: "FetchSupplyLeaves", Handler: _Universe_FetchSupplyLeaves_Handler, }, + { + MethodName: "InsertSupplyCommit", + Handler: _Universe_InsertSupplyCommit_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "universerpc/universe.proto", From 80e58e05ac8a88543f3e90ae2b2525c5aaf57deb Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 12 Aug 2025 23:30:46 +0100 Subject: [PATCH 14/51] taprpc: remove FetchSupplyLeaves from public uni whitelist Remove the FetchSupplyLeaves RPC endpoint from the public universe whitelist, as it will primarily be used by integration tests from now on. --- taprpc/perms.go | 1 - 1 file changed, 1 deletion(-) diff --git a/taprpc/perms.go b/taprpc/perms.go index 94135f48a..6bffd98bb 100644 --- a/taprpc/perms.go +++ b/taprpc/perms.go @@ -374,7 +374,6 @@ func MacaroonWhitelist(allowUniPublicAccessRead bool, // nolint: lll if allowUniPublicAccessRead || allowPublicUniProofCourier { whitelist["/universerpc.Universe/QueryProof"] = struct{}{} - whitelist["/universerpc.Universe/FetchSupplyLeaves"] = struct{}{} whitelist["/universerpc.Universe/FetchSupplyCommit"] = struct{}{} whitelist["/authmailboxrpc.Mailbox/ReceiveMessages"] = struct{}{} } From 5897e62547c43fb1af574c2183b11f89136bf13c Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 12 Aug 2025 23:48:45 +0100 Subject: [PATCH 15/51] multi: move inclusion proof feature to FetchSupplyLeaves RPC The FetchSupplyCommit RPC previously accepted leaf keys and returned inclusion proofs for those leaves. This feature is now moved to the FetchSupplyLeaves RPC. The FetchSupplyLeaves endpoint is not served publicly by the universe server (mostly used in itests), whereas FetchSupplyCommit is public. Limiting FetchSupplyCommit avoids on-the-fly inclusion proof generation. FetchSupplyCommit is geared towards supply commit syncing. --- cmd/commands/universe.go | 70 +- itest/supply_commit_test.go | 29 +- rpcserver.go | 106 +-- taprpc/universerpc/universe.pb.go | 818 +++++++++++------------ taprpc/universerpc/universe.proto | 52 +- taprpc/universerpc/universe.swagger.json | 120 ++-- universe/supplycommit/manager.go | 14 + 7 files changed, 630 insertions(+), 579 deletions(-) diff --git a/cmd/commands/universe.go b/cmd/commands/universe.go index ecbc0d956..fc74e9548 100644 --- a/cmd/commands/universe.go +++ b/cmd/commands/universe.go @@ -1638,21 +1638,6 @@ var fetchSupplyCommitCmd = cli.Command{ Usage: "the group key of the asset group to fetch", Required: true, }, - &cli.StringSliceFlag{ - Name: "issuance_leaf_keys", - Usage: "a list of issuance leaf keys to fetch " + - "inclusion proofs for", - }, - &cli.StringSliceFlag{ - Name: "burn_leaf_keys", - Usage: "a list of burn leaf keys to fetch inclusion " + - "proofs for", - }, - &cli.StringSliceFlag{ - Name: "ignore_leaf_keys", - Usage: "a list of ignore leaf keys to fetch " + - "inclusion proofs for", - }, }, Action: fetchSupplyCommit, } @@ -1668,26 +1653,6 @@ func fetchSupplyCommit(ctx *cli.Context) error { }, } - issuanceKeys, err := parseHexStrings( - ctx.StringSlice("issuance_leaf_keys"), - ) - if err != nil { - return fmt.Errorf("invalid issuance_leaf_keys: %w", err) - } - req.IssuanceLeafKeys = issuanceKeys - - burnKeys, err := parseHexStrings(ctx.StringSlice("burn_leaf_keys")) - if err != nil { - return fmt.Errorf("invalid burn_leaf_keys: %w", err) - } - req.BurnLeafKeys = burnKeys - - ignoreKeys, err := parseHexStrings(ctx.StringSlice("ignore_leaf_keys")) - if err != nil { - return fmt.Errorf("invalid ignore_leaf_keys: %w", err) - } - req.IgnoreLeafKeys = ignoreKeys - resp, err := client.FetchSupplyCommit(cliCtx, req) if err != nil { return err @@ -1719,6 +1684,21 @@ var fetchSupplyLeavesCmd = cli.Command{ Usage: "the end of the block height range", Required: true, }, + &cli.StringSliceFlag{ + Name: "issuance_leaf_keys", + Usage: "a list of issuance leaf keys to fetch " + + "inclusion proofs for", + }, + &cli.StringSliceFlag{ + Name: "burn_leaf_keys", + Usage: "a list of burn leaf keys to fetch inclusion " + + "proofs for", + }, + &cli.StringSliceFlag{ + Name: "ignore_leaf_keys", + Usage: "a list of ignore leaf keys to fetch " + + "inclusion proofs for", + }, }, Action: fetchSupplyLeaves, } @@ -1736,6 +1716,26 @@ func fetchSupplyLeaves(ctx *cli.Context) error { BlockHeightEnd: uint32(ctx.Uint64("block_height_end")), } + issuanceKeys, err := parseHexStrings( + ctx.StringSlice("issuance_leaf_keys"), + ) + if err != nil { + return fmt.Errorf("invalid issuance_leaf_keys: %w", err) + } + req.IssuanceLeafKeys = issuanceKeys + + burnKeys, err := parseHexStrings(ctx.StringSlice("burn_leaf_keys")) + if err != nil { + return fmt.Errorf("invalid burn_leaf_keys: %w", err) + } + req.BurnLeafKeys = burnKeys + + ignoreKeys, err := parseHexStrings(ctx.StringSlice("ignore_leaf_keys")) + if err != nil { + return fmt.Errorf("invalid ignore_leaf_keys: %w", err) + } + req.IgnoreLeafKeys = ignoreKeys + resp, err := client.FetchSupplyLeaves(cliCtx, req) if err != nil { return err diff --git a/itest/supply_commit_test.go b/itest/supply_commit_test.go index 1e3f11777..95dd0f448 100644 --- a/itest/supply_commit_test.go +++ b/itest/supply_commit_test.go @@ -247,10 +247,6 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ GroupKeyBytes: groupKeyBytes, }, - IgnoreLeafKeys: [][]byte{ - respIgnore.LeafKey, - respIgnore2.LeafKey, - }, }, ) require.Nil(t.t, fetchRespNil) @@ -285,10 +281,6 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ GroupKeyBytes: groupKeyBytes, }, - IgnoreLeafKeys: [][]byte{ - respIgnore.LeafKey, - respIgnore2.LeafKey, - }, }, ) require.NoError(t.t, err) @@ -328,10 +320,27 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { ignoreRootLeafKey, supplyTreeIgnoreLeafNode, ) + // Now fetch the inclusion proofs using FetchSupplyLeaves instead of + // FetchSupplyCommit. + t.Log("Fetch supply leaves with inclusion proofs") + // nolint: lll + fetchLeavesResp, err := t.tapd.FetchSupplyLeaves( + ctxb, &unirpc.FetchSupplyLeavesRequest{ + GroupKey: &unirpc.FetchSupplyLeavesRequest_GroupKeyBytes{ + GroupKeyBytes: groupKeyBytes, + }, + IgnoreLeafKeys: [][]byte{ + respIgnore.LeafKey, + respIgnore2.LeafKey, + }, + }, + ) + require.NoError(t.t, err) + // Unmarshal ignore tree leaf inclusion proof to verify that the // ignored asset outpoint is included in the ignore tree. - require.Len(t.t, fetchResp.IgnoreLeafInclusionProofs, 2) - inclusionProofBytes := fetchResp.IgnoreLeafInclusionProofs[0] + require.Len(t.t, fetchLeavesResp.IgnoreLeafInclusionProofs, 2) + inclusionProofBytes := fetchLeavesResp.IgnoreLeafInclusionProofs[0] // Verify that the ignore tree root can be computed from the ignore leaf // inclusion proof. diff --git a/rpcserver.go b/rpcserver.go index 4aee100e2..322e0d4ce 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4331,38 +4331,6 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, "root: %w", err) } - // Get inclusion proofs for any issuance leaf key specified in the - // request. - issuanceTree := resp.Subtrees[supplycommit.MintTreeType] - issuanceInclusionProofs, err := inclusionProofs( - ctx, issuanceTree, req.IssuanceLeafKeys, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch issuance tree "+ - "inclusion proofs: %w", err) - } - - // Get inclusion proofs for any burn leaf key specified in the request. - burnTree := resp.Subtrees[supplycommit.BurnTreeType] - burnInclusionProofs, err := inclusionProofs( - ctx, burnTree, req.BurnLeafKeys, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch burn tree "+ - "inclusion proofs: %w", err) - } - - // Get inclusion proofs for any ignore leaf key specified in the - // request. - ignoreTree := resp.Subtrees[supplycommit.IgnoreTreeType] - ignoreInclusionProofs, err := inclusionProofs( - ctx, ignoreTree, req.IgnoreLeafKeys, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch ignore tree "+ - "inclusion proofs: %w", err) - } - // Sanity check: ensure the supply root derived from the supply tree // matches the root provided in the chain commitment. if resp.ChainCommitment.SupplyRoot.NodeHash() != @@ -4408,10 +4376,6 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, IssuanceSubtreeRoot: rpcIssuanceSubtreeRoot, BurnSubtreeRoot: rpcBurnSubtreeRoot, IgnoreSubtreeRoot: rpcIgnoreSubtreeRoot, - - IssuanceLeafInclusionProofs: issuanceInclusionProofs, - BurnLeafInclusionProofs: burnInclusionProofs, - IgnoreLeafInclusionProofs: ignoreInclusionProofs, }, nil } @@ -4461,6 +4425,24 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, return nil, fmt.Errorf("failed to fetch supply leaves: %w", err) } + // Check if inclusion proofs are requested. + needsInclusionProofs := len(req.IssuanceLeafKeys) > 0 || + len(req.BurnLeafKeys) > 0 || len(req.IgnoreLeafKeys) > 0 + + // If inclusion proofs are requested, fetch the subtrees. + var subtrees supplycommit.SupplyTrees + if needsInclusionProofs { + subtreeResult, err := r.cfg.SupplyCommitManager.FetchSubTrees( + ctx, assetSpec, + ) + if err != nil { + return nil, fmt.Errorf("failed to fetch subtrees for "+ + "inclusion proofs: %w", err) + } + + subtrees = subtreeResult + } + rpcMarshalLeafEntry := func(leafEntry supplycommit.SupplyUpdateEvent) ( *unirpc.SupplyLeafEntry, error) { @@ -4552,10 +4534,56 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, rpcIgnoreLeaves = append(rpcIgnoreLeaves, rpcLeaf) } + // Generate inclusion proofs if requested. + var ( + issuanceInclusionProofs [][]byte + burnInclusionProofs [][]byte + ignoreInclusionProofs [][]byte + ) + + if needsInclusionProofs { + // Get inclusion proofs for any issuance leaf key specified in + // the request. + issuanceTree := subtrees[supplycommit.MintTreeType] + var err error + issuanceInclusionProofs, err = inclusionProofs( + ctx, issuanceTree, req.IssuanceLeafKeys, + ) + if err != nil { + return nil, fmt.Errorf("failed to fetch issuance tree "+ + "inclusion proofs: %w", err) + } + + // Get inclusion proofs for any burn leaf key specified in the + // request. + burnTree := subtrees[supplycommit.BurnTreeType] + burnInclusionProofs, err = inclusionProofs( + ctx, burnTree, req.BurnLeafKeys, + ) + if err != nil { + return nil, fmt.Errorf("failed to fetch burn tree "+ + "inclusion proofs: %w", err) + } + + // Get inclusion proofs for any ignore leaf key specified in the + // request. + ignoreTree := subtrees[supplycommit.IgnoreTreeType] + ignoreInclusionProofs, err = inclusionProofs( + ctx, ignoreTree, req.IgnoreLeafKeys, + ) + if err != nil { + return nil, fmt.Errorf("failed to fetch ignore tree "+ + "inclusion proofs: %w", err) + } + } + return &unirpc.FetchSupplyLeavesResponse{ - IssuanceLeaves: rpcIssuanceLeaves, - BurnLeaves: rpcBurnLeaves, - IgnoreLeaves: rpcIgnoreLeaves, + IssuanceLeaves: rpcIssuanceLeaves, + BurnLeaves: rpcBurnLeaves, + IgnoreLeaves: rpcIgnoreLeaves, + IssuanceLeafInclusionProofs: issuanceInclusionProofs, + BurnLeafInclusionProofs: burnInclusionProofs, + IgnoreLeafInclusionProofs: ignoreInclusionProofs, }, nil } diff --git a/taprpc/universerpc/universe.pb.go b/taprpc/universerpc/universe.pb.go index 8a1d17538..5a386cf02 100644 --- a/taprpc/universerpc/universe.pb.go +++ b/taprpc/universerpc/universe.pb.go @@ -3670,15 +3670,6 @@ type FetchSupplyCommitRequest struct { // *FetchSupplyCommitRequest_GroupKeyBytes // *FetchSupplyCommitRequest_GroupKeyStr GroupKey isFetchSupplyCommitRequest_GroupKey `protobuf_oneof:"group_key"` - // Optional: A list of issuance leaf keys. For each key in this list, - // the endpoint will generate and return an inclusion proof. - IssuanceLeafKeys [][]byte `protobuf:"bytes,3,rep,name=issuance_leaf_keys,json=issuanceLeafKeys,proto3" json:"issuance_leaf_keys,omitempty"` - // Optional: A list of burn leaf keys. For each key in this list, - // the endpoint will generate and return an inclusion proof. - BurnLeafKeys [][]byte `protobuf:"bytes,4,rep,name=burn_leaf_keys,json=burnLeafKeys,proto3" json:"burn_leaf_keys,omitempty"` - // Optional: A list of ignore leaf keys. For each key in this list, the - // endpoint will generate and return an inclusion proof. - IgnoreLeafKeys [][]byte `protobuf:"bytes,5,rep,name=ignore_leaf_keys,json=ignoreLeafKeys,proto3" json:"ignore_leaf_keys,omitempty"` } func (x *FetchSupplyCommitRequest) Reset() { @@ -3734,27 +3725,6 @@ func (x *FetchSupplyCommitRequest) GetGroupKeyStr() string { return "" } -func (x *FetchSupplyCommitRequest) GetIssuanceLeafKeys() [][]byte { - if x != nil { - return x.IssuanceLeafKeys - } - return nil -} - -func (x *FetchSupplyCommitRequest) GetBurnLeafKeys() [][]byte { - if x != nil { - return x.BurnLeafKeys - } - return nil -} - -func (x *FetchSupplyCommitRequest) GetIgnoreLeafKeys() [][]byte { - if x != nil { - return x.IgnoreLeafKeys - } - return nil -} - type isFetchSupplyCommitRequest_GroupKey interface { isFetchSupplyCommitRequest_GroupKey() } @@ -3882,17 +3852,6 @@ type FetchSupplyCommitResponse struct { BurnSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,10,opt,name=burn_subtree_root,json=burnSubtreeRoot,proto3" json:"burn_subtree_root,omitempty"` // The root of the ignore tree for the specified asset. IgnoreSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,11,opt,name=ignore_subtree_root,json=ignoreSubtreeRoot,proto3" json:"ignore_subtree_root,omitempty"` - // Inclusion proofs for each issuance leaf key provided in the request. - // Each entry corresponds to the key at the same index in - // `issuance_leaf_keys`. - IssuanceLeafInclusionProofs [][]byte `protobuf:"bytes,12,rep,name=issuance_leaf_inclusion_proofs,json=issuanceLeafInclusionProofs,proto3" json:"issuance_leaf_inclusion_proofs,omitempty"` - // Inclusion proofs for each burn leaf key provided in the request. - // Each entry corresponds to the key at the same index in `burn_leaf_keys`. - BurnLeafInclusionProofs [][]byte `protobuf:"bytes,13,rep,name=burn_leaf_inclusion_proofs,json=burnLeafInclusionProofs,proto3" json:"burn_leaf_inclusion_proofs,omitempty"` - // Inclusion proofs for each ignored leaf key provided in the request. - // Each entry corresponds to the key at the same index in - // `ignore_leaf_keys`. - IgnoreLeafInclusionProofs [][]byte `protobuf:"bytes,14,rep,name=ignore_leaf_inclusion_proofs,json=ignoreLeafInclusionProofs,proto3" json:"ignore_leaf_inclusion_proofs,omitempty"` } func (x *FetchSupplyCommitResponse) Reset() { @@ -4004,27 +3963,6 @@ func (x *FetchSupplyCommitResponse) GetIgnoreSubtreeRoot() *SupplyCommitSubtreeR return nil } -func (x *FetchSupplyCommitResponse) GetIssuanceLeafInclusionProofs() [][]byte { - if x != nil { - return x.IssuanceLeafInclusionProofs - } - return nil -} - -func (x *FetchSupplyCommitResponse) GetBurnLeafInclusionProofs() [][]byte { - if x != nil { - return x.BurnLeafInclusionProofs - } - return nil -} - -func (x *FetchSupplyCommitResponse) GetIgnoreLeafInclusionProofs() [][]byte { - if x != nil { - return x.IgnoreLeafInclusionProofs - } - return nil -} - type FetchSupplyLeavesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4042,6 +3980,15 @@ type FetchSupplyLeavesRequest struct { BlockHeightStart uint32 `protobuf:"varint,3,opt,name=block_height_start,json=blockHeightStart,proto3" json:"block_height_start,omitempty"` // The end block height for the range of supply leaves to fetch. BlockHeightEnd uint32 `protobuf:"varint,4,opt,name=block_height_end,json=blockHeightEnd,proto3" json:"block_height_end,omitempty"` + // Optional: A list of issuance leaf keys. For each key in this list, + // the endpoint will generate and return an inclusion proof. + IssuanceLeafKeys [][]byte `protobuf:"bytes,5,rep,name=issuance_leaf_keys,json=issuanceLeafKeys,proto3" json:"issuance_leaf_keys,omitempty"` + // Optional: A list of burn leaf keys. For each key in this list, + // the endpoint will generate and return an inclusion proof. + BurnLeafKeys [][]byte `protobuf:"bytes,6,rep,name=burn_leaf_keys,json=burnLeafKeys,proto3" json:"burn_leaf_keys,omitempty"` + // Optional: A list of ignore leaf keys. For each key in this list, the + // endpoint will generate and return an inclusion proof. + IgnoreLeafKeys [][]byte `protobuf:"bytes,7,rep,name=ignore_leaf_keys,json=ignoreLeafKeys,proto3" json:"ignore_leaf_keys,omitempty"` } func (x *FetchSupplyLeavesRequest) Reset() { @@ -4111,6 +4058,27 @@ func (x *FetchSupplyLeavesRequest) GetBlockHeightEnd() uint32 { return 0 } +func (x *FetchSupplyLeavesRequest) GetIssuanceLeafKeys() [][]byte { + if x != nil { + return x.IssuanceLeafKeys + } + return nil +} + +func (x *FetchSupplyLeavesRequest) GetBurnLeafKeys() [][]byte { + if x != nil { + return x.BurnLeafKeys + } + return nil +} + +func (x *FetchSupplyLeavesRequest) GetIgnoreLeafKeys() [][]byte { + if x != nil { + return x.IgnoreLeafKeys + } + return nil +} + type isFetchSupplyLeavesRequest_GroupKey interface { isFetchSupplyLeavesRequest_GroupKey() } @@ -4284,6 +4252,17 @@ type FetchSupplyLeavesResponse struct { IssuanceLeaves []*SupplyLeafEntry `protobuf:"bytes,1,rep,name=issuance_leaves,json=issuanceLeaves,proto3" json:"issuance_leaves,omitempty"` BurnLeaves []*SupplyLeafEntry `protobuf:"bytes,2,rep,name=burn_leaves,json=burnLeaves,proto3" json:"burn_leaves,omitempty"` IgnoreLeaves []*SupplyLeafEntry `protobuf:"bytes,3,rep,name=ignore_leaves,json=ignoreLeaves,proto3" json:"ignore_leaves,omitempty"` + // Inclusion proofs for each issuance leaf key provided in the request. + // Each entry corresponds to the key at the same index in + // `issuance_leaf_keys`. + IssuanceLeafInclusionProofs [][]byte `protobuf:"bytes,4,rep,name=issuance_leaf_inclusion_proofs,json=issuanceLeafInclusionProofs,proto3" json:"issuance_leaf_inclusion_proofs,omitempty"` + // Inclusion proofs for each burn leaf key provided in the request. + // Each entry corresponds to the key at the same index in `burn_leaf_keys`. + BurnLeafInclusionProofs [][]byte `protobuf:"bytes,5,rep,name=burn_leaf_inclusion_proofs,json=burnLeafInclusionProofs,proto3" json:"burn_leaf_inclusion_proofs,omitempty"` + // Inclusion proofs for each ignored leaf key provided in the request. + // Each entry corresponds to the key at the same index in + // `ignore_leaf_keys`. + IgnoreLeafInclusionProofs [][]byte `protobuf:"bytes,6,rep,name=ignore_leaf_inclusion_proofs,json=ignoreLeafInclusionProofs,proto3" json:"ignore_leaf_inclusion_proofs,omitempty"` } func (x *FetchSupplyLeavesResponse) Reset() { @@ -4339,6 +4318,27 @@ func (x *FetchSupplyLeavesResponse) GetIgnoreLeaves() []*SupplyLeafEntry { return nil } +func (x *FetchSupplyLeavesResponse) GetIssuanceLeafInclusionProofs() [][]byte { + if x != nil { + return x.IssuanceLeafInclusionProofs + } + return nil +} + +func (x *FetchSupplyLeavesResponse) GetBurnLeafInclusionProofs() [][]byte { + if x != nil { + return x.BurnLeafInclusionProofs + } + return nil +} + +func (x *FetchSupplyLeavesResponse) GetIgnoreLeafInclusionProofs() [][]byte { + if x != nil { + return x.IgnoreLeafInclusionProofs + } + return nil +} + // SupplyCommitChainData represents the on-chain artifacts for a supply // commitment update. type SupplyCommitChainData struct { @@ -5087,361 +5087,361 @@ var file_universerpc_universe_proto_rawDesc = []byte{ 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf5, 0x01, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, 0x68, - 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, - 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, - 0x53, 0x74, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, - 0x10, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, - 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, - 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x62, 0x75, 0x72, 0x6e, 0x4c, - 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x67, 0x6e, 0x6f, 0x72, - 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0c, 0x52, 0x0e, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, - 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0xd6, - 0x01, 0x0a, 0x17, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, - 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x37, - 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x72, - 0x6f, 0x6f, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, 0x65, - 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x1b, 0x73, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, 0x73, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, 0x65, 0x65, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, - 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xcf, 0x06, 0x0a, 0x19, 0x46, 0x65, 0x74, 0x63, - 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x16, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, - 0x65, 0x52, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6e, 0x63, 0x68, 0x6f, - 0x72, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, - 0x63, 0x68, 0x6f, 0x72, 0x54, 0x78, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x11, 0x61, 0x6e, 0x63, 0x68, - 0x6f, 0x72, 0x5f, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x54, 0x78, 0x4f, 0x75, 0x74, - 0x49, 0x64, 0x78, 0x12, 0x3a, 0x0a, 0x1a, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x74, 0x78, - 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x54, - 0x78, 0x4f, 0x75, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, - 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, - 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x78, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x54, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x12, 0x74, 0x78, 0x5f, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x78, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x65, 0x65, 0x73, - 0x53, 0x61, 0x74, 0x73, 0x12, 0x58, 0x0a, 0x15, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, - 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x13, 0x69, 0x73, 0x73, 0x75, 0x61, - 0x6e, 0x63, 0x65, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x50, - 0x0a, 0x11, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, - 0x6f, 0x6f, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, - 0x0f, 0x62, 0x75, 0x72, 0x6e, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, - 0x12, 0x54, 0x0a, 0x13, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, - 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, - 0x6f, 0x6f, 0x74, 0x52, 0x11, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x53, 0x75, 0x62, 0x74, 0x72, - 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x43, 0x0a, 0x1e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x1b, - 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, - 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x62, - 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52, - 0x17, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, - 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x69, 0x67, 0x6e, 0x6f, - 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x19, - 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, - 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x22, 0xcf, 0x01, 0x0a, 0x18, 0x46, 0x65, - 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, - 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, - 0x00, 0x52, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, - 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x45, 0x6e, 0x64, 0x42, 0x0b, - 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x7c, 0x0a, 0x0d, 0x53, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x08, - 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x19, - 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, 0xbf, 0x01, 0x0a, 0x0f, 0x53, 0x75, - 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x35, 0x0a, - 0x08, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, - 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x6c, 0x65, 0x61, - 0x66, 0x4b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6e, 0x6f, 0x64, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, - 0x6f, 0x64, 0x65, 0x52, 0x08, 0x6c, 0x65, 0x61, 0x66, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x4c, 0x65, 0x61, 0x66, 0x22, 0xe4, 0x01, 0x0a, 0x19, - 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, - 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x77, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, + 0x74, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, + 0xd6, 0x01, 0x0a, 0x17, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x37, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, + 0x72, 0x6f, 0x6f, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, + 0x65, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x1b, 0x73, 0x75, 0x70, + 0x70, 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, + 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, 0x65, 0x65, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, + 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x8c, 0x05, 0x0a, 0x19, 0x46, 0x65, 0x74, + 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x16, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, + 0x64, 0x65, 0x52, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6e, 0x63, 0x68, + 0x6f, 0x72, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, + 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x54, 0x78, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x11, 0x61, 0x6e, 0x63, + 0x68, 0x6f, 0x72, 0x5f, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x54, 0x78, 0x4f, 0x75, + 0x74, 0x49, 0x64, 0x78, 0x12, 0x3a, 0x0a, 0x1a, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x74, + 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, + 0x54, 0x78, 0x4f, 0x75, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x78, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x54, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x12, 0x74, 0x78, 0x5f, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x78, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x65, 0x65, + 0x73, 0x53, 0x61, 0x74, 0x73, 0x12, 0x58, 0x0a, 0x15, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, + 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x13, 0x69, 0x73, 0x73, 0x75, + 0x61, 0x6e, 0x63, 0x65, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, + 0x50, 0x0a, 0x11, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, + 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, + 0x52, 0x0f, 0x62, 0x75, 0x72, 0x6e, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x54, 0x0a, 0x13, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x74, + 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, + 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x11, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x53, 0x75, 0x62, 0x74, + 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0xcd, 0x02, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, + 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, + 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, + 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, + 0x79, 0x53, 0x74, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x45, 0x6e, 0x64, 0x12, 0x2c, 0x0a, 0x12, + 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, + 0x63, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75, + 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x0c, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, + 0x12, 0x28, 0x0a, 0x10, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, + 0x6b, 0x65, 0x79, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x69, 0x67, 0x6e, 0x6f, + 0x72, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x7c, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, 0xbf, 0x01, 0x0a, 0x0f, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x35, 0x0a, 0x08, 0x6c, 0x65, 0x61, + 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, + 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x52, + 0x08, 0x6c, 0x65, 0x61, 0x66, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x72, 0x61, 0x77, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x72, 0x61, 0x77, 0x4c, 0x65, 0x61, 0x66, 0x22, 0xa7, 0x03, 0x0a, 0x19, 0x46, 0x65, 0x74, 0x63, + 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, + 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, + 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, + 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, + 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, - 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, - 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, - 0x65, 0x73, 0x22, 0x8e, 0x03, 0x0a, 0x15, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, - 0x74, 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, 0x78, 0x6e, 0x12, 0x1c, - 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x08, 0x74, 0x78, 0x4f, 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, 0x21, 0x0a, 0x0c, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, - 0x1d, 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x28, - 0x0a, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, - 0x52, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x53, 0x75, 0x6d, - 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x5f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x72, - 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x22, 0x84, 0x03, 0x0a, 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, - 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, - 0x72, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x44, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, - 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, - 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x43, + 0x0a, 0x1e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x1b, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, + 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, + 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, + 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, + 0x12, 0x3f, 0x0a, 0x1c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x19, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, + 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x73, 0x22, 0x8e, 0x03, 0x0a, 0x15, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x74, + 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, 0x78, 0x6e, 0x12, 0x1c, 0x0a, + 0x0a, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x08, 0x74, 0x78, 0x4f, 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, + 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, + 0x10, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, + 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x53, 0x75, 0x6d, 0x12, + 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x72, 0x6b, + 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x22, 0x84, 0x03, 0x0a, 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, + 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, + 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, + 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, + 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, + 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, + 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, + 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, + 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, - 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, - 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, - 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, - 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, - 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, - 0x52, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, - 0x79, 0x6e, 0x63, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, - 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, - 0x0d, 0x0a, 0x09, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, - 0x01, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, - 0x74, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, - 0x45, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, - 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, - 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, - 0x02, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, - 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, - 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, - 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, - 0x54, 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, - 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, - 0x48, 0x45, 0x49, 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, - 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, - 0x10, 0x07, 0x2a, 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, - 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, - 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, - 0x53, 0x43, 0x10, 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, - 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, - 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, - 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, - 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, - 0x42, 0x4c, 0x45, 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, - 0x0a, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, - 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, + 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, 0x09, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, 0x73, + 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, + 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, + 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, + 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x79, + 0x6e, 0x63, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, + 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, 0x01, + 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, + 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, + 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, + 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, + 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, 0x02, + 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, + 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, 0x10, + 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, + 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, + 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x48, + 0x45, 0x49, 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, + 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, 0x10, + 0x07, 0x2a, 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, + 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, + 0x43, 0x10, 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, + 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, + 0x13, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, + 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, + 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x42, + 0x4c, 0x45, 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, + 0x6f, 0x6f, 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, - 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, - 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, - 0x65, 0x73, 0x12, 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x44, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, - 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, - 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, - 0x72, 0x6f, 0x6f, 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, - 0x6f, 0x66, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, - 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, - 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, - 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, - 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x46, 0x0a, 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x12, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x6f, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x12, 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x49, 0x44, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, + 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x49, + 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, + 0x6f, 0x6f, 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3b, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, + 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, + 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x16, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x46, 0x0a, 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x12, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, - 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, - 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x2d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, - 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, + 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, - 0x0a, 0x13, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, - 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, - 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, - 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, - 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, - 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, + 0x13, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, + 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, + 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, - 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, - 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, - 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, - 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, - 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, + 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, + 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, + 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, + 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, + 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/taprpc/universerpc/universe.proto b/taprpc/universerpc/universe.proto index cf47fca3b..0cf12c38c 100644 --- a/taprpc/universerpc/universe.proto +++ b/taprpc/universerpc/universe.proto @@ -803,18 +803,6 @@ message FetchSupplyCommitRequest { // REST). string group_key_str = 2; } - - // Optional: A list of issuance leaf keys. For each key in this list, - // the endpoint will generate and return an inclusion proof. - repeated bytes issuance_leaf_keys = 3; - - // Optional: A list of burn leaf keys. For each key in this list, - // the endpoint will generate and return an inclusion proof. - repeated bytes burn_leaf_keys = 4; - - // Optional: A list of ignore leaf keys. For each key in this list, the - // endpoint will generate and return an inclusion proof. - repeated bytes ignore_leaf_keys = 5; } message SupplyCommitSubtreeRoot { @@ -870,20 +858,6 @@ message FetchSupplyCommitResponse { // The root of the ignore tree for the specified asset. SupplyCommitSubtreeRoot ignore_subtree_root = 11; - - // Inclusion proofs for each issuance leaf key provided in the request. - // Each entry corresponds to the key at the same index in - // `issuance_leaf_keys`. - repeated bytes issuance_leaf_inclusion_proofs = 12; - - // Inclusion proofs for each burn leaf key provided in the request. - // Each entry corresponds to the key at the same index in `burn_leaf_keys`. - repeated bytes burn_leaf_inclusion_proofs = 13; - - // Inclusion proofs for each ignored leaf key provided in the request. - // Each entry corresponds to the key at the same index in - // `ignore_leaf_keys`. - repeated bytes ignore_leaf_inclusion_proofs = 14; } message FetchSupplyLeavesRequest { @@ -903,6 +877,18 @@ message FetchSupplyLeavesRequest { // The end block height for the range of supply leaves to fetch. uint32 block_height_end = 4; + + // Optional: A list of issuance leaf keys. For each key in this list, + // the endpoint will generate and return an inclusion proof. + repeated bytes issuance_leaf_keys = 5; + + // Optional: A list of burn leaf keys. For each key in this list, + // the endpoint will generate and return an inclusion proof. + repeated bytes burn_leaf_keys = 6; + + // Optional: A list of ignore leaf keys. For each key in this list, the + // endpoint will generate and return an inclusion proof. + repeated bytes ignore_leaf_keys = 7; } // SupplyLeafKey identifies a supply leaf entry. It contains the components @@ -939,6 +925,20 @@ message FetchSupplyLeavesResponse { repeated SupplyLeafEntry issuance_leaves = 1; repeated SupplyLeafEntry burn_leaves = 2; repeated SupplyLeafEntry ignore_leaves = 3; + + // Inclusion proofs for each issuance leaf key provided in the request. + // Each entry corresponds to the key at the same index in + // `issuance_leaf_keys`. + repeated bytes issuance_leaf_inclusion_proofs = 4; + + // Inclusion proofs for each burn leaf key provided in the request. + // Each entry corresponds to the key at the same index in `burn_leaf_keys`. + repeated bytes burn_leaf_inclusion_proofs = 5; + + // Inclusion proofs for each ignored leaf key provided in the request. + // Each entry corresponds to the key at the same index in + // `ignore_leaf_keys`. + repeated bytes ignore_leaf_inclusion_proofs = 6; } // SupplyCommitChainData represents the on-chain artifacts for a supply diff --git a/taprpc/universerpc/universe.swagger.json b/taprpc/universerpc/universe.swagger.json index 4bf5d5994..386080922 100644 --- a/taprpc/universerpc/universe.swagger.json +++ b/taprpc/universerpc/universe.swagger.json @@ -1423,6 +1423,42 @@ "required": false, "type": "integer", "format": "int64" + }, + { + "name": "issuance_leaf_keys", + "description": "Optional: A list of issuance leaf keys. For each key in this list,\nthe endpoint will generate and return an inclusion proof.", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "collectionFormat": "multi" + }, + { + "name": "burn_leaf_keys", + "description": "Optional: A list of burn leaf keys. For each key in this list,\nthe endpoint will generate and return an inclusion proof.", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "collectionFormat": "multi" + }, + { + "name": "ignore_leaf_keys", + "description": "Optional: A list of ignore leaf keys. For each key in this list, the\nendpoint will generate and return an inclusion proof.", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "collectionFormat": "multi" } ], "tags": [ @@ -1503,42 +1539,6 @@ "required": false, "type": "string", "format": "byte" - }, - { - "name": "issuance_leaf_keys", - "description": "Optional: A list of issuance leaf keys. For each key in this list,\nthe endpoint will generate and return an inclusion proof.", - "in": "query", - "required": false, - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "collectionFormat": "multi" - }, - { - "name": "burn_leaf_keys", - "description": "Optional: A list of burn leaf keys. For each key in this list,\nthe endpoint will generate and return an inclusion proof.", - "in": "query", - "required": false, - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "collectionFormat": "multi" - }, - { - "name": "ignore_leaf_keys", - "description": "Optional: A list of ignore leaf keys. For each key in this list, the\nendpoint will generate and return an inclusion proof.", - "in": "query", - "required": false, - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "collectionFormat": "multi" } ], "tags": [ @@ -2520,30 +2520,6 @@ "ignore_subtree_root": { "$ref": "#/definitions/universerpcSupplyCommitSubtreeRoot", "description": "The root of the ignore tree for the specified asset." - }, - "issuance_leaf_inclusion_proofs": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "description": "Inclusion proofs for each issuance leaf key provided in the request.\nEach entry corresponds to the key at the same index in\n`issuance_leaf_keys`." - }, - "burn_leaf_inclusion_proofs": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "description": "Inclusion proofs for each burn leaf key provided in the request.\nEach entry corresponds to the key at the same index in `burn_leaf_keys`." - }, - "ignore_leaf_inclusion_proofs": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "description": "Inclusion proofs for each ignored leaf key provided in the request.\nEach entry corresponds to the key at the same index in\n`ignore_leaf_keys`." } } }, @@ -2570,6 +2546,30 @@ "type": "object", "$ref": "#/definitions/universerpcSupplyLeafEntry" } + }, + "issuance_leaf_inclusion_proofs": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "Inclusion proofs for each issuance leaf key provided in the request.\nEach entry corresponds to the key at the same index in\n`issuance_leaf_keys`." + }, + "burn_leaf_inclusion_proofs": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "Inclusion proofs for each burn leaf key provided in the request.\nEach entry corresponds to the key at the same index in `burn_leaf_keys`." + }, + "ignore_leaf_inclusion_proofs": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "Inclusion proofs for each ignored leaf key provided in the request.\nEach entry corresponds to the key at the same index in\n`ignore_leaf_keys`." } } }, diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index b73d0d289..9dc31d8c9 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -557,6 +557,20 @@ func (m *Manager) FetchSupplyLeavesByHeight( return resp, nil } +// FetchSubTrees returns all the sub trees for the given asset specifier. +func (m *Manager) FetchSubTrees(ctx context.Context, + assetSpec asset.Specifier) (SupplyTrees, error) { + + var zero SupplyTrees + + subtrees, err := m.cfg.TreeView.FetchSubTrees(ctx, assetSpec).Unpack() + if err != nil { + return zero, fmt.Errorf("unable to fetch sub trees: %w", err) + } + + return subtrees, nil +} + // stateMachineCache is a thread-safe cache mapping an asset group's public key // to its supply commitment state machine. type stateMachineCache struct { From 02f97981723f39d2e81820d59e17fa7d16d9d300 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 20 Aug 2025 19:05:50 +0100 Subject: [PATCH 16/51] rpcserver: add helper function unmarshalGroupKey --- rpcserver.go | 175 ++++++++++++++++++--------------------------------- 1 file changed, 62 insertions(+), 113 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 322e0d4ce..7ebe46065 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4094,33 +4094,11 @@ func (r *rpcServer) UpdateSupplyCommit(ctx context.Context, req *unirpc.UpdateSupplyCommitRequest) ( *unirpc.UpdateSupplyCommitResponse, error) { - // Parse asset group key from the request. - var groupPubKey btcec.PublicKey - - switch { - case len(req.GetGroupKeyBytes()) > 0: - gk, err := btcec.ParsePubKey(req.GetGroupKeyBytes()) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - case len(req.GetGroupKeyStr()) > 0: - groupKeyBytes, err := hex.DecodeString(req.GetGroupKeyStr()) - if err != nil { - return nil, fmt.Errorf("decoding group key: %w", err) - } - - gk, err := btcec.ParsePubKey(groupKeyBytes) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - default: - return nil, fmt.Errorf("group key unspecified") + groupPubKey, err := unmarshalGroupKey( + req.GetGroupKeyBytes(), req.GetGroupKeyStr(), + ) + if err != nil { + return nil, fmt.Errorf("failed to parse group key: %w", err) } // We will now check to ensure that universe commitments are enabled for @@ -4128,7 +4106,7 @@ func (r *rpcServer) UpdateSupplyCommit(ctx context.Context, // // Look up the asset group by the group key. assetGroup, err := r.cfg.TapAddrBook.QueryAssetGroupByGroupKey( - ctx, &groupPubKey, + ctx, groupPubKey, ) if err != nil { return nil, fmt.Errorf("failed to find asset group "+ @@ -4150,7 +4128,7 @@ func (r *rpcServer) UpdateSupplyCommit(ctx context.Context, } // Formulate an asset specifier from the asset group key. - assetSpec := asset.NewSpecifierFromGroupKey(groupPubKey) + assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) // Send a commit tick event to the supply commitment manager. err = r.cfg.SupplyCommitManager.StartSupplyPublishFlow(ctx, assetSpec) @@ -4243,37 +4221,15 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, req *unirpc.FetchSupplyCommitRequest) ( *unirpc.FetchSupplyCommitResponse, error) { - // Parse asset group key from the request. - var groupPubKey btcec.PublicKey - - switch { - case len(req.GetGroupKeyBytes()) > 0: - gk, err := btcec.ParsePubKey(req.GetGroupKeyBytes()) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - case len(req.GetGroupKeyStr()) > 0: - groupKeyBytes, err := hex.DecodeString(req.GetGroupKeyStr()) - if err != nil { - return nil, fmt.Errorf("decoding group key: %w", err) - } - - gk, err := btcec.ParsePubKey(groupKeyBytes) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - default: - return nil, fmt.Errorf("group key unspecified") + groupPubKey, err := unmarshalGroupKey( + req.GetGroupKeyBytes(), req.GetGroupKeyStr(), + ) + if err != nil { + return nil, fmt.Errorf("failed to parse group key: %w", err) } // Formulate an asset specifier from the asset group key. - assetSpec := asset.NewSpecifierFromGroupKey(groupPubKey) + assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) // Fetch the supply commitment for the asset specifier. respOpt, err := r.cfg.SupplyCommitManager.FetchCommitment( @@ -4385,37 +4341,15 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, req *unirpc.FetchSupplyLeavesRequest) ( *unirpc.FetchSupplyLeavesResponse, error) { - // Parse asset group key from the request. - var groupPubKey btcec.PublicKey - - switch { - case len(req.GetGroupKeyBytes()) > 0: - gk, err := btcec.ParsePubKey(req.GetGroupKeyBytes()) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - case len(req.GetGroupKeyStr()) > 0: - groupKeyBytes, err := hex.DecodeString(req.GetGroupKeyStr()) - if err != nil { - return nil, fmt.Errorf("decoding group key: %w", err) - } - - gk, err := btcec.ParsePubKey(groupKeyBytes) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - default: - return nil, fmt.Errorf("group key unspecified") + groupPubKey, err := unmarshalGroupKey( + req.GetGroupKeyBytes(), req.GetGroupKeyStr(), + ) + if err != nil { + return nil, fmt.Errorf("failed to parse group key: %w", err) } // Formulate an asset specifier from the asset group key. - assetSpec := asset.NewSpecifierFromGroupKey(groupPubKey) + assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) // Fetch supply leaves for the asset specifier. resp, err := r.cfg.SupplyCommitManager.FetchSupplyLeavesByHeight( @@ -4804,33 +4738,11 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, req *unirpc.InsertSupplyCommitRequest) ( *unirpc.InsertSupplyCommitResponse, error) { - // Parse asset group key from the request. - var groupPubKey btcec.PublicKey - - switch { - case len(req.GetGroupKeyBytes()) > 0: - gk, err := btcec.ParsePubKey(req.GetGroupKeyBytes()) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - case len(req.GetGroupKeyStr()) > 0: - groupKeyBytes, err := hex.DecodeString(req.GetGroupKeyStr()) - if err != nil { - return nil, fmt.Errorf("decoding group key: %w", err) - } - - gk, err := btcec.ParsePubKey(groupKeyBytes) - if err != nil { - return nil, fmt.Errorf("parsing group key: %w", err) - } - - groupPubKey = *gk - - default: - return nil, fmt.Errorf("group key unspecified") + groupPubKey, err := unmarshalGroupKey( + req.GetGroupKeyBytes(), req.GetGroupKeyStr(), + ) + if err != nil { + return nil, fmt.Errorf("failed to parse group key: %w", err) } // Log the operation for debugging purposes. @@ -4901,7 +4813,7 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, len(supplyLeaves.BurnLeafEntries), len(supplyLeaves.IgnoreLeafEntries)) - assetSpec := asset.NewSpecifierFromGroupKey(groupPubKey) + assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) err = r.cfg.SupplyVerifyManager.InsertSupplyCommit( ctx, assetSpec, *rootCommitment, supplyLeaves, *chainProof, ) @@ -7022,6 +6934,43 @@ func unmarshalUniverseKey(key *unirpc.UniverseKey) (universe.Identifier, return uniID, leafKey, nil } +// unmarshalGroupKey attempts to parse a group key from either the byte slice +// or hex string form. If the group key is specified in both forms, the byte +// slice form takes precedence (which usually can't be the case because we use +// this for `oneof` gRPC fields that can't specify both). +func unmarshalGroupKey(groupKeyBytes []byte, + groupKeyStr string) (*btcec.PublicKey, error) { + + switch { + case len(groupKeyBytes) > 0: + gk, err := btcec.ParsePubKey(groupKeyBytes) + if err != nil { + return nil, fmt.Errorf("error parsing group key: %w", + err) + } + + return gk, nil + + case len(groupKeyStr) > 0: + groupKeyBytes, err := hex.DecodeString(groupKeyStr) + if err != nil { + return nil, fmt.Errorf("error decoding group key: %w", + err) + } + + gk, err := btcec.ParsePubKey(groupKeyBytes) + if err != nil { + return nil, fmt.Errorf("error parsing group key: %w", + err) + } + + return gk, nil + + default: + return nil, fmt.Errorf("group key unspecified") + } +} + // unmarshalAssetLeaf unmarshals an asset leaf from the RPC form. func unmarshalAssetLeaf(leaf *unirpc.AssetLeaf) (*universe.Leaf, error) { // We'll just pull the asset details from the serialized issuance proof From 94a761e0be38c385f2914cb551423a6a3f3d0374 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 17:48:15 +0100 Subject: [PATCH 17/51] rpcserver: add marshalSupplyLeaves and unmarshalSupplyLeaves --- rpcserver.go | 270 +++++++++++++++++++++++++-------------------------- 1 file changed, 132 insertions(+), 138 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 7ebe46065..907fa6877 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4335,6 +4335,46 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, }, nil } +// mapSupplyLeaves is a generic helper that converts a slice of supply update +// events into a slice of RPC SupplyLeafEntry objects. +func mapSupplyLeaves[E any](entries []E) ([]*unirpc.SupplyLeafEntry, error) { + return fn.MapErr(entries, func(i E) (*unirpc.SupplyLeafEntry, error) { + interfaceType, ok := any(&i).(supplycommit.SupplyUpdateEvent) + if !ok { + return nil, fmt.Errorf("expected supply update event, "+ + "got %T", i) + } + return marshalSupplyUpdateEvent(interfaceType) + }) +} + +// marshalSupplyLeaves converts a SupplyLeaves struct into the corresponding +// RPC SupplyLeafEntry slices for issuance, burn, and ignore leaves. +func marshalSupplyLeaves( + leaves supplycommit.SupplyLeaves) ([]*unirpc.SupplyLeafEntry, + []*unirpc.SupplyLeafEntry, []*unirpc.SupplyLeafEntry, error) { + + rpcIssuanceLeaves, err := mapSupplyLeaves(leaves.IssuanceLeafEntries) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to marshal issuance "+ + "leaf: %w", err) + } + + rpcBurnLeaves, err := mapSupplyLeaves(leaves.BurnLeafEntries) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to marshal burn "+ + "leaf: %w", err) + } + + rpcIgnoreLeaves, err := mapSupplyLeaves(leaves.IgnoreLeafEntries) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to marshal burn "+ + "leaf: %w", err) + } + + return rpcIssuanceLeaves, rpcBurnLeaves, rpcIgnoreLeaves, nil +} + // FetchSupplyLeaves returns the set of supply leaves for the given asset // specifier within the specified height range. func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, @@ -4377,95 +4417,12 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, subtrees = subtreeResult } - rpcMarshalLeafEntry := func(leafEntry supplycommit.SupplyUpdateEvent) ( - *unirpc.SupplyLeafEntry, error) { - - leafNode, err := leafEntry.UniverseLeafNode() - if err != nil { - rpcsLog.Errorf("Failed to get universe leaf node "+ - "from leaf entry: %v (leaf_entry=%s)", err, - spew.Sdump(leafEntry)) - - return nil, fmt.Errorf("failed to get universe leaf "+ - "node from leaf entry: %w", err) - } - - leafKey := leafEntry.UniverseLeafKey() - - outPoint := leafKey.LeafOutPoint() - rpcOutPoint := unirpc.Outpoint{ - HashStr: outPoint.Hash.String(), - Index: int32(outPoint.Index), - } - - // Encode the leaf as a byte slice. - var leafBuf bytes.Buffer - err = leafEntry.Encode(&leafBuf) - if err != nil { - return nil, fmt.Errorf("failed to encode leaf entry: "+ - "%w", err) - } - - return &unirpc.SupplyLeafEntry{ - LeafKey: &unirpc.SupplyLeafKey{ - Outpoint: &rpcOutPoint, - ScriptKey: schnorr.SerializePubKey( - leafKey.LeafScriptKey().PubKey, - ), - AssetId: fn.ByteSlice(leafKey.LeafAssetID()), - }, - LeafNode: marshalMssmtNode(leafNode), - BlockHeight: leafEntry.BlockHeight(), - RawLeaf: leafBuf.Bytes(), - }, nil - } - - // Marshal issuance supply leaves into the RPC format. - rpcIssuanceLeaves := make( - []*unirpc.SupplyLeafEntry, 0, len(resp.IssuanceLeafEntries), - ) - for idx := range resp.IssuanceLeafEntries { - leafEntry := resp.IssuanceLeafEntries[idx] - - rpcLeaf, err := rpcMarshalLeafEntry(&leafEntry) - if err != nil { - return nil, fmt.Errorf("failed to marshal supply "+ - "leaf entry: %w", err) - } - - rpcIssuanceLeaves = append(rpcIssuanceLeaves, rpcLeaf) - } - - // Marshal burn supply leaves into the RPC format. - rpcBurnLeaves := make( - []*unirpc.SupplyLeafEntry, 0, len(resp.BurnLeafEntries), - ) - for idx := range resp.BurnLeafEntries { - leafEntry := resp.BurnLeafEntries[idx] - - rpcLeaf, err := rpcMarshalLeafEntry(&leafEntry) - if err != nil { - return nil, fmt.Errorf("failed to marshal supply "+ - "leaf entry: %w", err) - } - - rpcBurnLeaves = append(rpcBurnLeaves, rpcLeaf) - } - - // Marshal ignore supply leaves into the RPC format. - rpcIgnoreLeaves := make( - []*unirpc.SupplyLeafEntry, 0, len(resp.IgnoreLeafEntries), + issuanceLeaves, burnLeaves, ignoreLeaves, err := marshalSupplyLeaves( + resp, ) - for idx := range resp.IgnoreLeafEntries { - leafEntry := resp.IgnoreLeafEntries[idx] - - rpcLeaf, err := rpcMarshalLeafEntry(&leafEntry) - if err != nil { - return nil, fmt.Errorf("failed to marshal supply "+ - "leaf entry: %w", err) - } - - rpcIgnoreLeaves = append(rpcIgnoreLeaves, rpcLeaf) + if err != nil { + return nil, fmt.Errorf("unable to marshal supply leaves: %w", + err) } // Generate inclusion proofs if requested. @@ -4512,9 +4469,9 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, } return &unirpc.FetchSupplyLeavesResponse{ - IssuanceLeaves: rpcIssuanceLeaves, - BurnLeaves: rpcBurnLeaves, - IgnoreLeaves: rpcIgnoreLeaves, + IssuanceLeaves: issuanceLeaves, + BurnLeaves: burnLeaves, + IgnoreLeaves: ignoreLeaves, IssuanceLeafInclusionProofs: issuanceInclusionProofs, BurnLeafInclusionProofs: burnInclusionProofs, IgnoreLeafInclusionProofs: ignoreInclusionProofs, @@ -4640,6 +4597,47 @@ func unmarshalIgnoreSupplyLeaf( return ignoreEvent, nil } +// marshalSupplyUpdateEvent converts a SupplyUpdateEvent into an RPC +// SupplyLeafEntry. +func marshalSupplyUpdateEvent( + leafEntry supplycommit.SupplyUpdateEvent) (*unirpc.SupplyLeafEntry, + error) { + + leafNode, err := leafEntry.UniverseLeafNode() + if err != nil { + return nil, fmt.Errorf("unable to get universe leaf node "+ + "from leaf entry: %w", err) + } + + leafKey := leafEntry.UniverseLeafKey() + + outPoint := leafKey.LeafOutPoint() + rpcOutPoint := unirpc.Outpoint{ + HashStr: outPoint.Hash.String(), + Index: int32(outPoint.Index), + } + + // Encode the leaf as a byte slice. + var leafBuf bytes.Buffer + err = leafEntry.Encode(&leafBuf) + if err != nil { + return nil, fmt.Errorf("unable to encode leaf entry: %w", err) + } + + return &unirpc.SupplyLeafEntry{ + LeafKey: &unirpc.SupplyLeafKey{ + Outpoint: &rpcOutPoint, + ScriptKey: schnorr.SerializePubKey( + leafKey.LeafScriptKey().PubKey, + ), + AssetId: fn.ByteSlice(leafKey.LeafAssetID()), + }, + LeafNode: marshalMssmtNode(leafNode), + BlockHeight: leafEntry.BlockHeight(), + RawLeaf: leafBuf.Bytes(), + }, nil +} + // unmarshalSupplyCommitChainData converts an RPC SupplyCommitChainData into // both a supplycommit.RootCommitment and supplycommit.ChainProof. func unmarshalSupplyCommitChainData( @@ -4732,6 +4730,43 @@ func unmarshalSupplyCommitChainData( return rootCommitment, chainProof, nil } +// unmarshalSupplyLeaves converts the RPC supply leaves into a SupplyLeaves +// struct that can be used by the supply commitment verifier. +func unmarshalSupplyLeaves(issuanceLeaves, burnLeaves, + ignoreLeaves []*unirpc.SupplyLeafEntry) (*supplycommit.SupplyLeaves, + error) { + + var ( + supplyLeaves supplycommit.SupplyLeaves + err error + ) + supplyLeaves.IssuanceLeafEntries, err = fn.MapErrWithPtr( + issuanceLeaves, unmarshalMintSupplyLeaf, + ) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal mint event: %w", + err) + } + + supplyLeaves.BurnLeafEntries, err = fn.MapErrWithPtr( + burnLeaves, unmarshalBurnSupplyLeaf, + ) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal burn event: %w", + err) + } + + supplyLeaves.IgnoreLeafEntries, err = fn.MapErrWithPtr( + ignoreLeaves, unmarshalIgnoreSupplyLeaf, + ) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal ignore event: %w", + err) + } + + return &supplyLeaves, nil +} + // InsertSupplyCommit stores a verified supply commitment for the given // asset group in the node's local database. func (r *rpcServer) InsertSupplyCommit(ctx context.Context, @@ -4758,53 +4793,12 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, err) } - // Initialize the SupplyLeaves structure to collect all unmarshalled - // events. - var supplyLeaves supplycommit.SupplyLeaves - - // Process issuance leaves. - supplyLeaves.IssuanceLeafEntries = make( - []supplycommit.NewMintEvent, 0, len(req.IssuanceLeaves), + supplyLeaves, err := unmarshalSupplyLeaves( + req.IssuanceLeaves, req.BurnLeaves, req.IgnoreLeaves, ) - for _, rpcLeaf := range req.IssuanceLeaves { - mintEvent, err := unmarshalMintSupplyLeaf(rpcLeaf) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal issuance "+ - "leaf: %w", err) - } - supplyLeaves.IssuanceLeafEntries = append( - supplyLeaves.IssuanceLeafEntries, *mintEvent, - ) - } - - // Process burn leaves. - supplyLeaves.BurnLeafEntries = make( - []supplycommit.NewBurnEvent, 0, len(req.BurnLeaves), - ) - for _, rpcLeaf := range req.BurnLeaves { - burnEvent, err := unmarshalBurnSupplyLeaf(rpcLeaf) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal burn "+ - "leaf: %w", err) - } - supplyLeaves.BurnLeafEntries = append( - supplyLeaves.BurnLeafEntries, *burnEvent, - ) - } - - // Process ignore leaves. - supplyLeaves.IgnoreLeafEntries = make( - []supplycommit.NewIgnoreEvent, 0, len(req.IgnoreLeaves), - ) - for _, rpcLeaf := range req.IgnoreLeaves { - ignoreEvent, err := unmarshalIgnoreSupplyLeaf(rpcLeaf) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal ignore "+ - "leaf: %w", err) - } - supplyLeaves.IgnoreLeafEntries = append( - supplyLeaves.IgnoreLeafEntries, *ignoreEvent, - ) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal supply leaves: %w", + err) } rpcsLog.Debugf("Successfully unmarshalled commitment, %d issuance, "+ @@ -4815,7 +4809,7 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) err = r.cfg.SupplyVerifyManager.InsertSupplyCommit( - ctx, assetSpec, *rootCommitment, supplyLeaves, *chainProof, + ctx, assetSpec, *rootCommitment, *supplyLeaves, *chainProof, ) if err != nil { return nil, fmt.Errorf("failed to insert supply commitment: %w", From f991952a55b9dda5702f774b29dd198c7f8e7c98 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 17:08:12 +0100 Subject: [PATCH 18/51] supplycommit: push supply commit to remote uni in CommitFinalizeState Update supply commit construction state machine to push the generated supply commitment and diff leaves to the remote universe server. We will pass the supply syncer into the supply commit manager in a separate future commit once RocSupplySync is in place. --- universe/supplycommit/env.go | 57 +++++++++++ universe/supplycommit/manager.go | 6 ++ universe/supplycommit/mock.go | 23 +++++ universe/supplycommit/state_machine_test.go | 105 ++++++++++++++------ universe/supplycommit/transitions.go | 58 ++++++++++- 5 files changed, 215 insertions(+), 34 deletions(-) diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 59ec933cf..d75ffdc51 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "fmt" + "net/url" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" @@ -102,6 +103,39 @@ type SupplyLeaves struct { IgnoreLeafEntries []NewIgnoreEvent } +// NewSupplyLeavesFromEvents creates a SupplyLeaves instance from a slice of +// SupplyUpdateEvent instances. +func NewSupplyLeavesFromEvents(events []SupplyUpdateEvent) (SupplyLeaves, + error) { + + var leaves SupplyLeaves + for idx := range events { + event := events[idx] + + switch e := event.(type) { + case *NewMintEvent: + leaves.IssuanceLeafEntries = append( + leaves.IssuanceLeafEntries, *e, + ) + + case *NewBurnEvent: + leaves.BurnLeafEntries = append( + leaves.BurnLeafEntries, *e, + ) + + case *NewIgnoreEvent: + leaves.IgnoreLeafEntries = append( + leaves.IgnoreLeafEntries, *e, + ) + + default: + return leaves, fmt.Errorf("unknown event type: %T", e) + } + } + + return leaves, nil +} + // AssetLookup is an interface that allows us to query for asset // information, such as asset groups and asset metadata. type AssetLookup interface { @@ -535,6 +569,25 @@ type StateMachineStore interface { asset.Specifier) ([]SupplyUpdateEvent, error) } +// SupplySyncer is an interface that allows the state machine to insert +// supply commitments into the remote universe server. +type SupplySyncer interface { + // PushSupplyCommitment pushes a supply commitment to the remote + // universe server. This function should block until the sync insertion + // is complete. + // + // Returns a map of per-server errors keyed by server host string and + // an internal error. If all pushes succeed, both return values are nil. + // If some pushes fail, the map contains only the failed servers and + // their corresponding errors. If there's an internal/system error that + // prevents the operation from proceeding, it's returned as the second + // value. + PushSupplyCommitment(ctx context.Context, assetSpec asset.Specifier, + commitment RootCommitment, updateLeaves SupplyLeaves, + chainProof ChainProof, + canonicalUniverses []url.URL) (map[string]error, error) +} + // Environment is a set of dependencies that a state machine may need to carry // out the logic for a given state transition. All fields are to be considered // immutable, and will be fixed for the lifetime of the state machine. @@ -567,6 +620,10 @@ type Environment struct { // TODO(roasbeef): can make a slimmer version of Chain tapgarden.ChainBridge + // SupplySyncer is used to insert supply commitments into the remote + // universe server. + SupplySyncer SupplySyncer + // StateLog is the main state log that is used to track the state of the // state machine. This is used to persist the state of the state machine // across restarts. diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index 9dc31d8c9..fe0c0bbe1 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -65,6 +65,10 @@ type ManagerCfg struct { // TODO(roasbeef): can make a slimmer version of Chain tapgarden.ChainBridge + // SupplySyncer is used to insert supply commitments into the remote + // universe server. + SupplySyncer SupplySyncer + // DaemonAdapters is a set of adapters that allow the state machine to // interact with external daemons whilst processing internal events. DaemonAdapters DaemonAdapters @@ -232,8 +236,10 @@ func (m *Manager) fetchStateMachine( TreeView: m.cfg.TreeView, Commitments: m.cfg.Commitments, Wallet: m.cfg.Wallet, + AssetLookup: m.cfg.AssetLookup, KeyRing: m.cfg.KeyRing, Chain: m.cfg.Chain, + SupplySyncer: m.cfg.SupplySyncer, StateLog: m.cfg.StateLog, CommitConfTarget: DefaultCommitConfTarget, ChainParams: m.cfg.ChainParams, diff --git a/universe/supplycommit/mock.go b/universe/supplycommit/mock.go index 7e2e2123c..52ff11a82 100644 --- a/universe/supplycommit/mock.go +++ b/universe/supplycommit/mock.go @@ -2,6 +2,7 @@ package supplycommit import ( "context" + "net/url" "sync" "github.com/btcsuite/btcd/btcec/v2" @@ -459,3 +460,25 @@ func (m *mockAssetLookup) FetchInternalKeyLocator(ctx context.Context, args := m.Called(ctx, rawKey) return args.Get(0).(keychain.KeyLocator), args.Error(1) } + +// mockSupplySyncer is a mock implementation of the SupplySyncer interface. +type mockSupplySyncer struct { + mock.Mock +} + +func (m *mockSupplySyncer) PushSupplyCommitment(ctx context.Context, + assetSpec asset.Specifier, commitment RootCommitment, + updateLeaves SupplyLeaves, chainProof ChainProof, + canonicalUniverses []url.URL) (map[string]error, error) { + + args := m.Called(ctx, assetSpec, commitment, updateLeaves, chainProof, + canonicalUniverses) + + // Handle both nil and map[string]error return types. + var errorMap map[string]error + if args.Get(0) != nil { + errorMap = args.Get(0).(map[string]error) + } + + return errorMap, args.Error(1) +} diff --git a/universe/supplycommit/state_machine_test.go b/universe/supplycommit/state_machine_test.go index a581427c1..73e4ec83e 100644 --- a/universe/supplycommit/state_machine_test.go +++ b/universe/supplycommit/state_machine_test.go @@ -120,16 +120,17 @@ type supplyCommitTestHarness struct { stateMachine *StateMachine env *Environment - mockTreeView *mockSupplyTreeView - mockCommits *mockCommitmentTracker - mockWallet *mockWallet - mockKeyRing *mockKeyRing - mockChain *mockChainBridge - mockStateLog *mockStateMachineStore - mockCache *mockIgnoreCheckerCache - mockDaemon *mockDaemonAdapters - mockErrReporter *mockErrorReporter - mockAssetLookup *mockAssetLookup + mockTreeView *mockSupplyTreeView + mockCommits *mockCommitmentTracker + mockWallet *mockWallet + mockKeyRing *mockKeyRing + mockChain *mockChainBridge + mockStateLog *mockStateMachineStore + mockCache *mockIgnoreCheckerCache + mockDaemon *mockDaemonAdapters + mockErrReporter *mockErrorReporter + mockAssetLookup *mockAssetLookup + mockSupplySyncer *mockSupplySyncer stateSub protofsm.StateSubscriber[Event, *Environment] } @@ -147,6 +148,7 @@ func newSupplyCommitTestHarness(t *testing.T, mockErrReporter := &mockErrorReporter{} mockCache := &mockIgnoreCheckerCache{} mockAssetLookup := &mockAssetLookup{} + mockSupplySyncer := &mockSupplySyncer{} env := &Environment{ AssetSpec: cfg.assetSpec, @@ -157,6 +159,7 @@ func newSupplyCommitTestHarness(t *testing.T, Chain: mockChain, StateLog: mockStateLog, AssetLookup: mockAssetLookup, + SupplySyncer: mockSupplySyncer, CommitConfTarget: DefaultCommitConfTarget, IgnoreCheckerCache: mockCache, } @@ -174,20 +177,21 @@ func newSupplyCommitTestHarness(t *testing.T, stateMachine := protofsm.NewStateMachine(fsmCfg) h := &supplyCommitTestHarness{ - t: t, - cfg: cfg, - stateMachine: &stateMachine, - env: env, - mockTreeView: mockTreeView, - mockCommits: mockCommits, - mockWallet: mockWallet, - mockKeyRing: mockKey, - mockChain: mockChain, - mockStateLog: mockStateLog, - mockCache: mockCache, - mockDaemon: mockDaemon, - mockErrReporter: mockErrReporter, - mockAssetLookup: mockAssetLookup, + t: t, + cfg: cfg, + stateMachine: &stateMachine, + env: env, + mockTreeView: mockTreeView, + mockCommits: mockCommits, + mockWallet: mockWallet, + mockKeyRing: mockKey, + mockChain: mockChain, + mockStateLog: mockStateLog, + mockCache: mockCache, + mockDaemon: mockDaemon, + mockErrReporter: mockErrReporter, + mockAssetLookup: mockAssetLookup, + mockSupplySyncer: mockSupplySyncer, } h.stateSub = stateMachine.RegisterStateEvents() @@ -294,6 +298,7 @@ func (h *supplyCommitTestHarness) expectFullCommitmentCycleMocks( h.expectPsbtSigning() h.expectInsertSignedCommitTx() h.expectAssetLookup() + h.expectSupplySyncer() h.expectBroadcastAndConfRegistration() } @@ -561,6 +566,16 @@ func (h *supplyCommitTestHarness) expectAssetLookup() { ).Return(dummyMetaReveal, nil).Maybe() } +// expectSupplySyncer sets up the mock expectations for SupplySyncer calls. +func (h *supplyCommitTestHarness) expectSupplySyncer() { + h.t.Helper() + + h.mockSupplySyncer.On( + "PushSupplyCommitment", mock.Anything, mock.Anything, + mock.Anything, mock.Anything, mock.Anything, mock.Anything, + ).Return(nil, nil).Maybe() +} + // expectFreezePendingTransition sets up the mock expectation for the // FreezePendingTransition call. func (h *supplyCommitTestHarness) expectFreezePendingTransition() { @@ -647,7 +662,10 @@ func TestSupplyCommitUpdatesPendingStateTransitions(t *testing.T) { t.Parallel() testScriptKey := test.RandPubKey(t) - defaultAssetSpec := asset.NewSpecifierFromId(testAssetID) + randGroupKey := test.RandPubKey(t) + defaultAssetSpec := asset.NewSpecifierOptionalGroupPubKey( + testAssetID, randGroupKey, + ) initialMintEvent := newTestMintEvent(t, testScriptKey, randOutPoint(t)) // Verify that when the UpdatesPendingState receives a @@ -761,7 +779,10 @@ func TestSupplyCommitUpdatesPendingStateTransitions(t *testing.T) { func TestSupplyCommitTreeCreateStateTransitions(t *testing.T) { t.Parallel() - defaultAssetSpec := asset.NewSpecifierFromId(testAssetID) + randGroupKey := test.RandPubKey(t) + defaultAssetSpec := asset.NewSpecifierOptionalGroupPubKey( + testAssetID, randGroupKey, + ) mintEvent := newTestMintEvent(t, test.RandPubKey(t), randOutPoint(t)) // Verify that a CommitTickEvent received by the CommitTreeCreateState @@ -853,7 +874,11 @@ func TestSupplyCommitTreeCreateStateTransitions(t *testing.T) { func TestSupplyCommitTxCreateStateTransitions(t *testing.T) { t.Parallel() - defaultAssetSpec := asset.NewSpecifierFromId(testAssetID) + randGroupKey := test.RandPubKey(t) + defaultAssetSpec := asset.NewSpecifierOptionalGroupPubKey( + testAssetID, randGroupKey, + ) + initialTransition := SupplyStateTransition{ NewCommitment: RootCommitment{ SupplyRoot: mssmt.NewBranch( @@ -861,6 +886,7 @@ func TestSupplyCommitTxCreateStateTransitions(t *testing.T) { mssmt.NewLeafNode([]byte("right"), 0), ), }, + ChainProof: lfn.Some(ChainProof{}), } // Verify that a CreateTxEvent received by the CommitTxCreateState leads @@ -937,7 +963,11 @@ func TestSupplyCommitTxCreateStateTransitions(t *testing.T) { func TestSupplyCommitTxSignStateTransitions(t *testing.T) { t.Parallel() - defaultAssetSpec := asset.NewSpecifierFromId(testAssetID) + randGroupKey := test.RandPubKey(t) + defaultAssetSpec := asset.NewSpecifierOptionalGroupPubKey( + testAssetID, randGroupKey, + ) + dummyTx := wire.NewMsgTx(2) dummyTx.AddTxOut(&wire.TxOut{PkScript: []byte("test"), Value: 1}) @@ -949,6 +979,7 @@ func TestSupplyCommitTxSignStateTransitions(t *testing.T) { InternalKey: internalKey, TxOutIdx: 0, }, + ChainProof: lfn.Some(ChainProof{}), } // This test verifies that a SignTxEvent received by the @@ -1061,6 +1092,7 @@ func TestSupplyCommitBroadcastStateTransitions(t *testing.T) { signedPsbt := newTestSignedPsbt(t, dummyTx) h.expectAssetLookup() + h.expectSupplySyncer() h.expectBroadcastAndConfRegistration() broadcastEvent := &BroadcastEvent{ @@ -1087,6 +1119,7 @@ func TestSupplyCommitBroadcastStateTransitions(t *testing.T) { defer h.stopAndAssert() h.expectAssetLookup() + h.expectSupplySyncer() h.expectCommitState() h.expectApplyStateTransition() @@ -1131,6 +1164,7 @@ func TestSupplyCommitBroadcastStateTransitions(t *testing.T) { defer h.stopAndAssert() h.expectAssetLookup() + h.expectSupplySyncer() h.expectApplyStateTransition() // Mock the binding of dangling updates to return a new set of @@ -1228,6 +1262,7 @@ func TestSupplyCommitFinalizeStateTransitions(t *testing.T) { mssmt.NewLeafNode([]byte("leaf"), 0), ), }, + ChainProof: lfn.Some(ChainProof{}), } // This test verifies that a FinalizeEvent received by the @@ -1243,6 +1278,8 @@ func TestSupplyCommitFinalizeStateTransitions(t *testing.T) { h.start() defer h.stopAndAssert() + h.expectAssetLookup() + h.expectSupplySyncer() h.expectApplyStateTransition() h.expectBindDanglingUpdatesWithEvents([]SupplyUpdateEvent{}) h.expectIgnoreCheckerCacheInvalidation() @@ -1295,9 +1332,6 @@ func TestSupplyCommitFinalizeStateTransitions(t *testing.T) { t.Run("finalize_with_asset_id_specifier", func(t *testing.T) { assetIDSpec := asset.NewSpecifierFromId(testAssetID) - expectedErr := errors.New("group key must be specified for " + - "supply tree: unable to unwrap asset group public key") - h := newSupplyCommitTestHarness(t, &harnessCfg{ initialState: &CommitFinalizeState{ SupplyTransition: initialTransition, @@ -1307,7 +1341,8 @@ func TestSupplyCommitFinalizeStateTransitions(t *testing.T) { h.start() defer h.stopAndAssert() - h.expectApplyStateTransition() + expectedErr := errors.New("unable to fetch latest asset " + + "metadata: unable to unwrap asset group public key") h.expectFailure(expectedErr) finalizeEvent := &FinalizeEvent{} @@ -1792,7 +1827,9 @@ func TestDanglingUpdatesFullCycle(t *testing.T) { defer h.stopAndAssert() // Freeze the pending transition when we start the commit cycle, and set - // up the mocks that we need.. + // up the mocks that we need. + h.expectAssetLookup() + h.expectSupplySyncer() h.expectFreezePendingTransition() h.expectFullCommitmentCycleMocks(true) @@ -1932,6 +1969,8 @@ func TestDanglingUpdatesAcrossStates(t *testing.T) { &CommitBroadcastState{}, ) + h.expectAssetLookup() + h.expectSupplySyncer() h.expectCommitState() h.expectApplyStateTransition() diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index 4204755b3..110bf410e 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "net/url" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" @@ -1082,13 +1083,68 @@ func (c *CommitFinalizeState) ProcessEvent(event Event, prefixedLog.Infof("Finalizing supply commitment transition") + // Insert the finalized supply transition into the remote + // universe server via the syncer. + chainProof, err := c.SupplyTransition.ChainProof.UnwrapOrErr( + fmt.Errorf("supply transition in finalize state " + + "must have chain proof"), + ) + if err != nil { + return nil, err + } + + // Retrieve latest canonical universe list from the latest + // metadata for the asset group. + metadata, err := fetchLatestAssetMetadata( + ctx, env.AssetLookup, env.AssetSpec, + ) + if err != nil { + return nil, fmt.Errorf("unable to fetch latest asset "+ + "metadata: %w", err) + } + + // Insert the supply commitment into the remote universes. This + // call should block until push is complete. + canonicalUniverses := metadata.CanonicalUniverses.UnwrapOr( + []url.URL{}, + ) + + supplyLeaves, err := NewSupplyLeavesFromEvents( + c.SupplyTransition.PendingUpdates, + ) + if err != nil { + return nil, fmt.Errorf("unable to create "+ + "supply leaves from pending updates: %w", err) + } + + serverErrors, err := env.SupplySyncer.PushSupplyCommitment( + ctx, env.AssetSpec, c.SupplyTransition.NewCommitment, + supplyLeaves, chainProof, canonicalUniverses, + ) + if err != nil { + return nil, fmt.Errorf("unable to insert "+ + "supply commitment into remote universe "+ + "server via syncer: %w", err) + } + + // Log any per-server errors but continue with the operation. + // + // TODO(ffranr): Handle the case where we fail to push to + // all servers. Also, if push fails because of + // ErrPrevCommitmentNotFound then we need to sync older + // commitments first. + for serverHost, serverErr := range serverErrors { + prefixedLog.Warnf("Failed to push supply commitment "+ + "to server %s: %v", serverHost, serverErr) + } + // At this point, the commitment has been confirmed on disk, so // we can update: the state machine state on disk, and swap in // all the new supply tree information. // // First, we'll update the supply state on disk. This way when // we restart his is idempotent. - err := env.StateLog.ApplyStateTransition( + err = env.StateLog.ApplyStateTransition( ctx, env.AssetSpec, c.SupplyTransition, ) if err != nil { From 818e87f4f7e0fc132574e2391aae16b1362b23b6 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 22 Aug 2025 23:05:07 +0100 Subject: [PATCH 19/51] tapdb+universe: consolidate code, link commitment to previous one We clean up and consolidate a bunch of code: - Parsing the supply commitment and data was duplicated, is now a single function. - The commitmentChainInfo can be merged directly into the commitment.CommitmentBlock (add fields BlockHeader and MerkleProof), gets rid of a parameter. We also add a new spent_commitment field to the supply_commitments table that tracks the previous commitment transaction that was spent, to create the full chain. To be able to traverse that chain, we also need to be able to query the commitments either by a commitment's outpoint or by the outpoint a commitment is spending, which allows backward lookup in a loop. --- tapdb/sqlc/querier.go | 5 +- tapdb/sqlc/queries/supply_commit.sql | 55 +++- tapdb/sqlc/supply_commit.sql.go | 242 +++++++++++---- tapdb/supply_commit.go | 440 +++++++++++++++++---------- tapdb/supply_commit_test.go | 47 ++- universe/supplycommit/env.go | 13 +- universe/supplyverifier/env.go | 28 ++ 7 files changed, 555 insertions(+), 275 deletions(-) diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 44286ae11..7547003cd 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -188,8 +188,11 @@ type Querier interface { QueryPassiveAssets(ctx context.Context, transferID int64) ([]QueryPassiveAssetsRow, error) QueryPendingSupplyCommitTransition(ctx context.Context, groupKey []byte) (QueryPendingSupplyCommitTransitionRow, error) QueryProofTransferAttempts(ctx context.Context, arg QueryProofTransferAttemptsParams) ([]time.Time, error) + QueryStartingSupplyCommitment(ctx context.Context, groupKey []byte) (QueryStartingSupplyCommitmentRow, error) QuerySupplyCommitStateMachine(ctx context.Context, groupKey []byte) (QuerySupplyCommitStateMachineRow, error) - QuerySupplyCommitment(ctx context.Context, commitID int64) (SupplyCommitment, error) + QuerySupplyCommitment(ctx context.Context, commitID int64) (QuerySupplyCommitmentRow, error) + QuerySupplyCommitmentByOutpoint(ctx context.Context, arg QuerySupplyCommitmentByOutpointParams) (QuerySupplyCommitmentByOutpointRow, error) + QuerySupplyCommitmentBySpentOutpoint(ctx context.Context, arg QuerySupplyCommitmentBySpentOutpointParams) (QuerySupplyCommitmentBySpentOutpointRow, error) QuerySupplyLeavesByHeight(ctx context.Context, arg QuerySupplyLeavesByHeightParams) ([]QuerySupplyLeavesByHeightRow, error) QuerySupplyUpdateEvents(ctx context.Context, transitionID sql.NullInt64) ([]QuerySupplyUpdateEventsRow, error) // TODO(roasbeef): use the universe id instead for the grouping? so namespace diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index bb6291e7a..066c0b8e6 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -30,11 +30,11 @@ RETURNING current_state_id, latest_commitment_id; -- name: InsertSupplyCommitment :one INSERT INTO supply_commitments ( group_key, chain_txn_id, - output_index, internal_key_id, output_key, -- Core fields + output_index, internal_key_id, output_key, spent_commitment, -- Core fields block_height, block_header, merkle_proof, -- Nullable chain details supply_root_hash, supply_root_sum -- Nullable root details ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 ) RETURNING commit_id; -- name: UpdateSupplyCommitmentChainDetails :exec @@ -134,10 +134,45 @@ SET transition_id = @transition_id WHERE group_key = @group_key AND transition_id IS NULL; -- name: QuerySupplyCommitment :one -SELECT * -FROM supply_commitments +SELECT sqlc.embed(sc), ct.tx_index +FROM supply_commitments AS sc +JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id WHERE commit_id = @commit_id; +-- name: QuerySupplyCommitmentByOutpoint :one +SELECT sqlc.embed(sc), ct.tx_index +FROM supply_commitments AS sc +JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.group_key = @group_key AND + sc.output_index = @output_index AND + ct.txid = @txid; + +-- name: QuerySupplyCommitmentBySpentOutpoint :one +WITH spent_commitment AS ( + SELECT ssc.commit_id + FROM supply_commitments AS ssc + JOIN chain_txns AS ct + ON ssc.chain_txn_id = ct.txn_id + WHERE ssc.group_key = @group_key AND + ssc.output_index = @output_index AND + ct.txid = @txid +) +SELECT sqlc.embed(sc), ct.tx_index +FROM supply_commitments AS sc + JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.spent_commitment = (SELECT commit_id FROM spent_commitment); + +-- name: QueryStartingSupplyCommitment :one +SELECT sqlc.embed(sc), ct.tx_index +FROM supply_commitments AS sc + JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.spent_commitment IS NULL AND + sc.group_key = @group_key; + -- name: UpdateSupplyCommitTransitionCommitment :exec UPDATE supply_commit_transitions SET new_commitment_id = @new_commitment_id, @@ -181,17 +216,7 @@ WHERE outpoint = @outpoint -- name: FetchSupplyCommit :one SELECT - sc.commit_id, - sc.output_index, - sc.output_key, - sqlc.embed(ik), - txn.raw_tx, - txn.block_height, - txn.block_hash, - txn.tx_index, - txn.chain_fees, - sc.supply_root_hash AS root_hash, - sc.supply_root_sum AS root_sum + sqlc.embed(sc), txn.tx_index FROM supply_commit_state_machines sm JOIN supply_commitments sc ON sm.latest_commitment_id = sc.commit_id diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index a060e6dc9..745383f11 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -69,17 +69,7 @@ func (q *Queries) FetchInternalKeyByID(ctx context.Context, keyID int64) (FetchI const FetchSupplyCommit = `-- name: FetchSupplyCommit :one SELECT - sc.commit_id, - sc.output_index, - sc.output_key, - ik.key_id, ik.raw_key, ik.key_family, ik.key_index, - txn.raw_tx, - txn.block_height, - txn.block_hash, - txn.tx_index, - txn.chain_fees, - sc.supply_root_hash AS root_hash, - sc.supply_root_sum AS root_sum + sc.commit_id, sc.group_key, sc.chain_txn_id, sc.output_index, sc.internal_key_id, sc.output_key, sc.block_header, sc.block_height, sc.merkle_proof, sc.supply_root_hash, sc.supply_root_sum, sc.spent_commitment, txn.tx_index FROM supply_commit_state_machines sm JOIN supply_commitments sc ON sm.latest_commitment_id = sc.commit_id @@ -93,37 +83,27 @@ WHERE ` type FetchSupplyCommitRow struct { - CommitID int64 - OutputIndex sql.NullInt32 - OutputKey []byte - InternalKey InternalKey - RawTx []byte - BlockHeight sql.NullInt32 - BlockHash []byte - TxIndex sql.NullInt32 - ChainFees int64 - RootHash []byte - RootSum sql.NullInt64 + SupplyCommitment SupplyCommitment + TxIndex sql.NullInt32 } func (q *Queries) FetchSupplyCommit(ctx context.Context, groupKey []byte) (FetchSupplyCommitRow, error) { row := q.db.QueryRowContext(ctx, FetchSupplyCommit, groupKey) var i FetchSupplyCommitRow err := row.Scan( - &i.CommitID, - &i.OutputIndex, - &i.OutputKey, - &i.InternalKey.KeyID, - &i.InternalKey.RawKey, - &i.InternalKey.KeyFamily, - &i.InternalKey.KeyIndex, - &i.RawTx, - &i.BlockHeight, - &i.BlockHash, + &i.SupplyCommitment.CommitID, + &i.SupplyCommitment.GroupKey, + &i.SupplyCommitment.ChainTxnID, + &i.SupplyCommitment.OutputIndex, + &i.SupplyCommitment.InternalKeyID, + &i.SupplyCommitment.OutputKey, + &i.SupplyCommitment.BlockHeader, + &i.SupplyCommitment.BlockHeight, + &i.SupplyCommitment.MerkleProof, + &i.SupplyCommitment.SupplyRootHash, + &i.SupplyCommitment.SupplyRootSum, + &i.SupplyCommitment.SpentCommitment, &i.TxIndex, - &i.ChainFees, - &i.RootHash, - &i.RootSum, ) return i, err } @@ -248,25 +228,26 @@ func (q *Queries) InsertSupplyCommitTransition(ctx context.Context, arg InsertSu const InsertSupplyCommitment = `-- name: InsertSupplyCommitment :one INSERT INTO supply_commitments ( group_key, chain_txn_id, - output_index, internal_key_id, output_key, -- Core fields + output_index, internal_key_id, output_key, spent_commitment, -- Core fields block_height, block_header, merkle_proof, -- Nullable chain details supply_root_hash, supply_root_sum -- Nullable root details ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 ) RETURNING commit_id ` type InsertSupplyCommitmentParams struct { - GroupKey []byte - ChainTxnID int64 - OutputIndex sql.NullInt32 - InternalKeyID int64 - OutputKey []byte - BlockHeight sql.NullInt32 - BlockHeader []byte - MerkleProof []byte - SupplyRootHash []byte - SupplyRootSum sql.NullInt64 + GroupKey []byte + ChainTxnID int64 + OutputIndex sql.NullInt32 + InternalKeyID int64 + OutputKey []byte + SpentCommitment sql.NullInt64 + BlockHeight sql.NullInt32 + BlockHeader []byte + MerkleProof []byte + SupplyRootHash []byte + SupplyRootSum sql.NullInt64 } func (q *Queries) InsertSupplyCommitment(ctx context.Context, arg InsertSupplyCommitmentParams) (int64, error) { @@ -276,6 +257,7 @@ func (q *Queries) InsertSupplyCommitment(ctx context.Context, arg InsertSupplyCo arg.OutputIndex, arg.InternalKeyID, arg.OutputKey, + arg.SpentCommitment, arg.BlockHeight, arg.BlockHeader, arg.MerkleProof, @@ -447,6 +429,41 @@ func (q *Queries) QueryPendingSupplyCommitTransition(ctx context.Context, groupK return i, err } +const QueryStartingSupplyCommitment = `-- name: QueryStartingSupplyCommitment :one +SELECT sc.commit_id, sc.group_key, sc.chain_txn_id, sc.output_index, sc.internal_key_id, sc.output_key, sc.block_header, sc.block_height, sc.merkle_proof, sc.supply_root_hash, sc.supply_root_sum, sc.spent_commitment, ct.tx_index +FROM supply_commitments AS sc + JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.spent_commitment IS NULL AND + sc.group_key = $1 +` + +type QueryStartingSupplyCommitmentRow struct { + SupplyCommitment SupplyCommitment + TxIndex sql.NullInt32 +} + +func (q *Queries) QueryStartingSupplyCommitment(ctx context.Context, groupKey []byte) (QueryStartingSupplyCommitmentRow, error) { + row := q.db.QueryRowContext(ctx, QueryStartingSupplyCommitment, groupKey) + var i QueryStartingSupplyCommitmentRow + err := row.Scan( + &i.SupplyCommitment.CommitID, + &i.SupplyCommitment.GroupKey, + &i.SupplyCommitment.ChainTxnID, + &i.SupplyCommitment.OutputIndex, + &i.SupplyCommitment.InternalKeyID, + &i.SupplyCommitment.OutputKey, + &i.SupplyCommitment.BlockHeader, + &i.SupplyCommitment.BlockHeight, + &i.SupplyCommitment.MerkleProof, + &i.SupplyCommitment.SupplyRootHash, + &i.SupplyCommitment.SupplyRootSum, + &i.SupplyCommitment.SpentCommitment, + &i.TxIndex, + ) + return i, err +} + const QuerySupplyCommitStateMachine = `-- name: QuerySupplyCommitStateMachine :one SELECT sm.group_key, @@ -479,27 +496,126 @@ func (q *Queries) QuerySupplyCommitStateMachine(ctx context.Context, groupKey [] } const QuerySupplyCommitment = `-- name: QuerySupplyCommitment :one -SELECT commit_id, group_key, chain_txn_id, output_index, internal_key_id, output_key, block_header, block_height, merkle_proof, supply_root_hash, supply_root_sum, spent_commitment -FROM supply_commitments +SELECT sc.commit_id, sc.group_key, sc.chain_txn_id, sc.output_index, sc.internal_key_id, sc.output_key, sc.block_header, sc.block_height, sc.merkle_proof, sc.supply_root_hash, sc.supply_root_sum, sc.spent_commitment, ct.tx_index +FROM supply_commitments AS sc +JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id WHERE commit_id = $1 ` -func (q *Queries) QuerySupplyCommitment(ctx context.Context, commitID int64) (SupplyCommitment, error) { +type QuerySupplyCommitmentRow struct { + SupplyCommitment SupplyCommitment + TxIndex sql.NullInt32 +} + +func (q *Queries) QuerySupplyCommitment(ctx context.Context, commitID int64) (QuerySupplyCommitmentRow, error) { row := q.db.QueryRowContext(ctx, QuerySupplyCommitment, commitID) - var i SupplyCommitment + var i QuerySupplyCommitmentRow err := row.Scan( - &i.CommitID, - &i.GroupKey, - &i.ChainTxnID, - &i.OutputIndex, - &i.InternalKeyID, - &i.OutputKey, - &i.BlockHeader, - &i.BlockHeight, - &i.MerkleProof, - &i.SupplyRootHash, - &i.SupplyRootSum, - &i.SpentCommitment, + &i.SupplyCommitment.CommitID, + &i.SupplyCommitment.GroupKey, + &i.SupplyCommitment.ChainTxnID, + &i.SupplyCommitment.OutputIndex, + &i.SupplyCommitment.InternalKeyID, + &i.SupplyCommitment.OutputKey, + &i.SupplyCommitment.BlockHeader, + &i.SupplyCommitment.BlockHeight, + &i.SupplyCommitment.MerkleProof, + &i.SupplyCommitment.SupplyRootHash, + &i.SupplyCommitment.SupplyRootSum, + &i.SupplyCommitment.SpentCommitment, + &i.TxIndex, + ) + return i, err +} + +const QuerySupplyCommitmentByOutpoint = `-- name: QuerySupplyCommitmentByOutpoint :one +SELECT sc.commit_id, sc.group_key, sc.chain_txn_id, sc.output_index, sc.internal_key_id, sc.output_key, sc.block_header, sc.block_height, sc.merkle_proof, sc.supply_root_hash, sc.supply_root_sum, sc.spent_commitment, ct.tx_index +FROM supply_commitments AS sc +JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.group_key = $1 AND + sc.output_index = $2 AND + ct.txid = $3 +` + +type QuerySupplyCommitmentByOutpointParams struct { + GroupKey []byte + OutputIndex sql.NullInt32 + Txid []byte +} + +type QuerySupplyCommitmentByOutpointRow struct { + SupplyCommitment SupplyCommitment + TxIndex sql.NullInt32 +} + +func (q *Queries) QuerySupplyCommitmentByOutpoint(ctx context.Context, arg QuerySupplyCommitmentByOutpointParams) (QuerySupplyCommitmentByOutpointRow, error) { + row := q.db.QueryRowContext(ctx, QuerySupplyCommitmentByOutpoint, arg.GroupKey, arg.OutputIndex, arg.Txid) + var i QuerySupplyCommitmentByOutpointRow + err := row.Scan( + &i.SupplyCommitment.CommitID, + &i.SupplyCommitment.GroupKey, + &i.SupplyCommitment.ChainTxnID, + &i.SupplyCommitment.OutputIndex, + &i.SupplyCommitment.InternalKeyID, + &i.SupplyCommitment.OutputKey, + &i.SupplyCommitment.BlockHeader, + &i.SupplyCommitment.BlockHeight, + &i.SupplyCommitment.MerkleProof, + &i.SupplyCommitment.SupplyRootHash, + &i.SupplyCommitment.SupplyRootSum, + &i.SupplyCommitment.SpentCommitment, + &i.TxIndex, + ) + return i, err +} + +const QuerySupplyCommitmentBySpentOutpoint = `-- name: QuerySupplyCommitmentBySpentOutpoint :one +WITH spent_commitment AS ( + SELECT ssc.commit_id + FROM supply_commitments AS ssc + JOIN chain_txns AS ct + ON ssc.chain_txn_id = ct.txn_id + WHERE ssc.group_key = $1 AND + ssc.output_index = $2 AND + ct.txid = $3 +) +SELECT sc.commit_id, sc.group_key, sc.chain_txn_id, sc.output_index, sc.internal_key_id, sc.output_key, sc.block_header, sc.block_height, sc.merkle_proof, sc.supply_root_hash, sc.supply_root_sum, sc.spent_commitment, ct.tx_index +FROM supply_commitments AS sc + JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.spent_commitment = (SELECT commit_id FROM spent_commitment) +` + +type QuerySupplyCommitmentBySpentOutpointParams struct { + GroupKey []byte + OutputIndex sql.NullInt32 + Txid []byte +} + +type QuerySupplyCommitmentBySpentOutpointRow struct { + SupplyCommitment SupplyCommitment + TxIndex sql.NullInt32 +} + +func (q *Queries) QuerySupplyCommitmentBySpentOutpoint(ctx context.Context, arg QuerySupplyCommitmentBySpentOutpointParams) (QuerySupplyCommitmentBySpentOutpointRow, error) { + row := q.db.QueryRowContext(ctx, QuerySupplyCommitmentBySpentOutpoint, arg.GroupKey, arg.OutputIndex, arg.Txid) + var i QuerySupplyCommitmentBySpentOutpointRow + err := row.Scan( + &i.SupplyCommitment.CommitID, + &i.SupplyCommitment.GroupKey, + &i.SupplyCommitment.ChainTxnID, + &i.SupplyCommitment.OutputIndex, + &i.SupplyCommitment.InternalKeyID, + &i.SupplyCommitment.OutputKey, + &i.SupplyCommitment.BlockHeader, + &i.SupplyCommitment.BlockHeight, + &i.SupplyCommitment.MerkleProof, + &i.SupplyCommitment.SupplyRootHash, + &i.SupplyCommitment.SupplyRootSum, + &i.SupplyCommitment.SpentCommitment, + &i.TxIndex, ) return i, err } diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index c2f643be7..25e8eac14 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -10,7 +10,6 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" @@ -18,18 +17,11 @@ import ( "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnutils" ) -// commitmentChainInfo holds optional chain confirmation details for a -// commitment. -type commitmentChainInfo struct { - BlockHeader *wire.BlockHeader - MerkleProof *proof.TxMerkleProof - BlockHeight uint32 -} - type ( // UnspentPrecommits is an alias for the sqlc type representing an // unspent pre-commitment row. @@ -145,9 +137,8 @@ type SupplyCommitStore interface { arg InsertSupplyUpdateEvent) error // UpsertChainTx upserts a chain transaction. - UpsertChainTx( - ctx context.Context, arg UpsertChainTxParams, - ) (int64, error) + UpsertChainTx(ctx context.Context, + arg UpsertChainTxParams) (int64, error) // UpdateSupplyCommitTransitionCommitment updates the pending commit tx // ID for a @@ -179,7 +170,24 @@ type SupplyCommitStore interface { // QuerySupplyCommitment fetches a specific supply commitment by ID. QuerySupplyCommitment(ctx context.Context, - commitID int64) (sqlc.SupplyCommitment, error) + commitID int64) (sqlc.QuerySupplyCommitmentRow, error) + + // QuerySupplyCommitmentByOutpoint fetches a supply commitment by its + // outpoint. + QuerySupplyCommitmentByOutpoint(ctx context.Context, + arg sqlc.QuerySupplyCommitmentByOutpointParams) ( + sqlc.QuerySupplyCommitmentByOutpointRow, error) + + // QuerySupplyCommitmentBySpentOutpoint fetches a supply commitment by + // its spent outpoint. + QuerySupplyCommitmentBySpentOutpoint(ctx context.Context, + arg sqlc.QuerySupplyCommitmentBySpentOutpointParams) ( + sqlc.QuerySupplyCommitmentBySpentOutpointRow, error) + + // QueryStartingSupplyCommitment fetches the very first supply + // commitment of an asset group. + QueryStartingSupplyCommitment(ctx context.Context, + groupKey []byte) (sqlc.QueryStartingSupplyCommitmentRow, error) // FetchChainTx fetches a chain transaction by its TXID. FetchChainTx(ctx context.Context, txid []byte) (ChainTxn, error) @@ -345,91 +353,15 @@ func (s *SupplyCommitMachine) SupplyCommit(ctx context.Context, err) } - internalKey, err := parseInternalKey(row.InternalKey) - if err != nil { - return fmt.Errorf("error parsing internal key: %w", err) - } - - outputKey, err := btcec.ParsePubKey(row.OutputKey) - if err != nil { - return fmt.Errorf("error parsing output key: %w", err) - } - - var commitTx wire.MsgTx - err = commitTx.Deserialize(bytes.NewReader(row.RawTx)) - if err != nil { - return fmt.Errorf("error deserializing commit tx: %w", - err) - } - - // Parse block related data from row if present. - var commitmentBlock fn.Option[supplycommit.CommitmentBlock] - if len(row.BlockHash) > 0 { - // Parse block height if present, otherwise return an - // error as it must be set if block hash is set. - if !row.BlockHeight.Valid { - return fmt.Errorf("block height must be set " + - "if block hash is set") - } - - blockHeight := uint32(row.BlockHeight.Int32) - - // Parse the block hash, which should be valid at this - // point. - blockHash, err := chainhash.NewHash(row.BlockHash) - if err != nil { - return fmt.Errorf("parsing block hash: %w", err) - } - - // Parse transaction block index which should be set - // if the block height is set. - if !row.TxIndex.Valid { - return fmt.Errorf("transaction index must be " + - "set if block height is set") - } - txIndex := uint32(row.TxIndex.Int32) - - commitmentBlock = fn.Some(supplycommit.CommitmentBlock{ - Hash: *blockHash, - Height: blockHeight, - TxIndex: txIndex, - ChainFees: row.ChainFees, - }) - } - - // Construct the root node directly from the stored hash and - // sum. Handle potential NULL values if the root wasn't set yet - // (though FetchSupplyCommit filters for confirmed TX, so it - // should be set). - var ( - rootHash mssmt.NodeHash - rootSum uint64 - rootNode *mssmt.BranchNode + rootCommitment, err := parseSupplyCommitmentRow( + ctx, row.SupplyCommitment, row.TxIndex, db, ) - if len(row.RootHash) != 0 && row.RootSum.Valid { - copy(rootHash[:], row.RootHash) - rootSum = uint64(row.RootSum.Int64) - rootNode = mssmt.NewComputedBranch(rootHash, rootSum) - } else { - // Should not happen due to query filter, but handle - // defensively. - log.Warnf("SupplyCommit: Fetched confirmed commit %d "+ - "but root hash/sum is NULL", row.CommitID) - - rootNode = mssmt.NewComputedBranch( - mssmt.EmptyTreeRootHash, 0, - ) + if err != nil { + return fmt.Errorf("failed to query commitment %d: %w", + row.SupplyCommitment.CommitID, err) } - rootCommitment := supplycommit.RootCommitment{ - Txn: &commitTx, - TxOutIdx: uint32(row.OutputIndex.Int32), - InternalKey: internalKey, - OutputKey: outputKey, - SupplyRoot: rootNode, - CommitmentBlock: commitmentBlock, - } - rootCommitmentOpt = lfn.Some(rootCommitment) + rootCommitmentOpt = lfn.Some(*rootCommitment) return nil }) @@ -845,8 +777,8 @@ func (s *SupplyCommitMachine) BindDanglingUpdatesToTransition( // InsertSignedCommitTx associates a new signed commitment anchor transaction // with the current active supply commitment state transition. func (s *SupplyCommitMachine) InsertSignedCommitTx(ctx context.Context, - assetSpec asset.Specifier, commitDetails supplycommit.SupplyCommitTxn, -) error { + assetSpec asset.Specifier, + commitDetails supplycommit.SupplyCommitTxn) error { groupKey := assetSpec.UnwrapGroupKeyToPtr() if groupKey == nil { @@ -905,24 +837,24 @@ func (s *SupplyCommitMachine) InsertSignedCommitTx(ctx context.Context, KeyIndex: int32(internalKeyDesc.Index), }) if err != nil { - return fmt.Errorf("failed to upsert "+ - "internal key %x: %w", + return fmt.Errorf("error upserting internal key %x: %w", internalKeyDesc.PubKey.SerializeCompressed(), err) } // Insert the new commitment record. Chain details (block // height, header, proof, output index) are NULL at this stage. - //nolint:lll - newCommitmentID, err := db.InsertSupplyCommitment(ctx, sqlc.InsertSupplyCommitmentParams{ - GroupKey: groupKeyBytes, - ChainTxnID: chainTxID, - InternalKeyID: internalKeyID, - OutputKey: outputKey.SerializeCompressed(), - SupplyRootHash: nil, - SupplyRootSum: sql.NullInt64{}, - OutputIndex: sqlInt32(outputIndex), - }) + params := sqlc.InsertSupplyCommitmentParams{ + GroupKey: groupKeyBytes, + ChainTxnID: chainTxID, + InternalKeyID: internalKeyID, + OutputKey: outputKey.SerializeCompressed(), + SupplyRootHash: nil, + SupplyRootSum: sql.NullInt64{}, + OutputIndex: sqlInt32(outputIndex), + SpentCommitment: pendingTransition.OldCommitmentID, + } + newCommitmentID, err := db.InsertSupplyCommitment(ctx, params) if err != nil { return fmt.Errorf("failed to insert new supply "+ "commitment: %w", err) @@ -1003,36 +935,206 @@ func (s *SupplyCommitMachine) CommitState(ctx context.Context, } // fetchCommitment is a helper to fetch and reconstruct a RootCommitment and -// its associated chain confirmation details. +// its associated chain confirmation details. If no commitment is found, +// it returns None for both the commitment and chain info. func fetchCommitment(ctx context.Context, db SupplyCommitStore, - commitID sql.NullInt64, groupKeyBytes []byte, -) (lfn.Option[supplycommit.RootCommitment], - lfn.Option[commitmentChainInfo], error) { + commitID sql.NullInt64) (lfn.Option[supplycommit.RootCommitment], + error) { noneRootCommit := lfn.None[supplycommit.RootCommitment]() - noneChainInfo := lfn.None[commitmentChainInfo]() if !commitID.Valid { - return noneRootCommit, noneChainInfo, nil + return noneRootCommit, nil } // First, fetch the supply commitment itself. - commit, err := db.QuerySupplyCommitment(ctx, commitID.Int64) + commitRow, err := db.QuerySupplyCommitment(ctx, commitID.Int64) if err != nil { if errors.Is(err, sql.ErrNoRows) { - return noneRootCommit, noneChainInfo, nil + return noneRootCommit, nil } - return noneRootCommit, noneChainInfo, fmt.Errorf("failed to "+ - "query commitment %d: %w", commitID.Int64, err) + return noneRootCommit, fmt.Errorf("failed to query "+ + "commitment %d: %w", commitID.Int64, err) } + commit, err := parseSupplyCommitmentRow( + ctx, commitRow.SupplyCommitment, commitRow.TxIndex, db, + ) + if err != nil { + return noneRootCommit, fmt.Errorf("failed to query "+ + "commitment %d: %w", commitID.Int64, err) + } + + return lfn.Some(*commit), nil +} + +// FetchCommitmentByOutpoint fetches a supply commitment by its outpoint and +// group key. If no commitment is found, it returns ErrCommitmentNotFound. +func (s *SupplyCommitMachine) FetchCommitmentByOutpoint(ctx context.Context, + assetSpec asset.Specifier, + outpoint wire.OutPoint) (*supplycommit.RootCommitment, error) { + + groupKey := assetSpec.UnwrapGroupKeyToPtr() + if groupKey == nil { + return nil, ErrMissingGroupKey + } + + var ( + writeTx = WriteTxOption() + groupKeyBytes = groupKey.SerializeCompressed() + commit *supplycommit.RootCommitment + ) + dbErr := s.db.ExecTx(ctx, writeTx, func(db SupplyCommitStore) error { + // First, fetch the supply commitment by group key and outpoint. + commitRow, err := db.QuerySupplyCommitmentByOutpoint( + ctx, sqlc.QuerySupplyCommitmentByOutpointParams{ + GroupKey: groupKeyBytes, + OutputIndex: sqlInt32(outpoint.Index), + Txid: outpoint.Hash[:], + }, + ) + if err != nil { + return fmt.Errorf("failed to query commitment for "+ + "outpoint %s: %w", outpoint, err) + } + + commit, err = parseSupplyCommitmentRow( + ctx, commitRow.SupplyCommitment, commitRow.TxIndex, db, + ) + if err != nil { + return fmt.Errorf("failed to parse commitment for "+ + "outpoint %s: %w", outpoint, err) + } + + return nil + }) + if dbErr != nil { + if errors.Is(dbErr, sql.ErrNoRows) { + return nil, supplyverifier.ErrCommitmentNotFound + } + + return nil, fmt.Errorf("failed to fetch commitment by "+ + "outpoint %s: %w", outpoint, dbErr) + } + + return commit, nil +} + +// FetchCommitmentBySpentOutpoint fetches a supply commitment by the outpoint it +// spent and group key. If no commitment is found, it returns +// ErrCommitmentNotFound. +func (s *SupplyCommitMachine) FetchCommitmentBySpentOutpoint( + ctx context.Context, assetSpec asset.Specifier, + spentOutpoint wire.OutPoint) (*supplycommit.RootCommitment, error) { + + groupKey := assetSpec.UnwrapGroupKeyToPtr() + if groupKey == nil { + return nil, ErrMissingGroupKey + } + + var ( + writeTx = WriteTxOption() + groupKeyBytes = groupKey.SerializeCompressed() + commit *supplycommit.RootCommitment + ) + dbErr := s.db.ExecTx(ctx, writeTx, func(db SupplyCommitStore) error { + // First, fetch the supply commitment by group key and outpoint. + commitRow, err := db.QuerySupplyCommitmentBySpentOutpoint( + ctx, sqlc.QuerySupplyCommitmentBySpentOutpointParams{ + GroupKey: groupKeyBytes, + OutputIndex: sqlInt32(spentOutpoint.Index), + Txid: spentOutpoint.Hash[:], + }, + ) + if err != nil { + return fmt.Errorf("failed to query commitment for "+ + "spent outpoint %s: %w", spentOutpoint, err) + } + + commit, err = parseSupplyCommitmentRow( + ctx, commitRow.SupplyCommitment, commitRow.TxIndex, db, + ) + if err != nil { + return fmt.Errorf("failed to parse commitment for "+ + "spent outpoint %s: %w", spentOutpoint, err) + } + + return nil + }) + if dbErr != nil { + if errors.Is(dbErr, sql.ErrNoRows) { + return nil, supplyverifier.ErrCommitmentNotFound + } + + return nil, fmt.Errorf("failed to fetch commitment by spent "+ + "outpoint %s: %w", spentOutpoint, dbErr) + } + + return commit, nil +} + +// FetchStartingCommitment fetches the very first supply commitment of an asset +// group. If no commitment is found, it returns ErrCommitmentNotFound. +func (s *SupplyCommitMachine) FetchStartingCommitment(ctx context.Context, + assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) { + + groupKey := assetSpec.UnwrapGroupKeyToPtr() + if groupKey == nil { + return nil, ErrMissingGroupKey + } + + var ( + writeTx = WriteTxOption() + groupKeyBytes = groupKey.SerializeCompressed() + commit *supplycommit.RootCommitment + ) + dbErr := s.db.ExecTx(ctx, writeTx, func(db SupplyCommitStore) error { + // First, fetch the supply commitment by group key. + commitRow, err := db.QueryStartingSupplyCommitment( + ctx, groupKeyBytes, + ) + if err != nil { + return fmt.Errorf("failed to query starting "+ + "commitment for group %x: %w", groupKeyBytes, + err) + } + + commit, err = parseSupplyCommitmentRow( + ctx, commitRow.SupplyCommitment, commitRow.TxIndex, db, + ) + if err != nil { + return fmt.Errorf("failed to parse starting "+ + "commitment for group %x: %w", groupKeyBytes, + err) + } + + return nil + }) + if dbErr != nil { + if errors.Is(dbErr, sql.ErrNoRows) { + return nil, supplyverifier.ErrCommitmentNotFound + } + + return nil, fmt.Errorf("failed to fetch starting commitment "+ + "for group %x: %w", groupKeyBytes, dbErr) + } + + return commit, nil +} + +// parseSupplyCommitmentRow parses a SupplyCommitment row into a +// supplycommit.RootCommitment and optional commitmentChainInfo. +func parseSupplyCommitmentRow(ctx context.Context, commit SupplyCommitment, + txIndex sql.NullInt32, + db SupplyCommitStore) (*supplycommit.RootCommitment, error) { + internalKeyRow, err := db.FetchInternalKeyByID( ctx, commit.InternalKeyID, ) if err != nil { - return noneRootCommit, noneChainInfo, fmt.Errorf("failed to "+ - "fetch internal key %d for commit %d: %w", - commit.InternalKeyID, commitID.Int64, err) + return nil, fmt.Errorf("failed to fetch internal key %d for "+ + "commit %d: %w", commit.InternalKeyID, commit.CommitID, + err) } internalKey, err := parseInternalKey(sqlc.InternalKey{ RawKey: internalKeyRow.RawKey, @@ -1040,39 +1142,36 @@ func fetchCommitment(ctx context.Context, db SupplyCommitStore, KeyIndex: internalKeyRow.KeyIndex, }) if err != nil { - return noneRootCommit, noneChainInfo, fmt.Errorf("failed to "+ - "parse internal key for commit %d: %w", commitID.Int64, - err) + return nil, fmt.Errorf("failed to parse internal key for "+ + "commit %d: %w", commit.CommitID, err) } outputKey, err := btcec.ParsePubKey(commit.OutputKey) if err != nil { - return noneRootCommit, noneChainInfo, fmt.Errorf("failed to "+ - "parse output key for commit %d: %w", commitID.Int64, - err) + return nil, fmt.Errorf("failed to parse output key for commit "+ + "%d: %w", commit.CommitID, err) } // Fetch and deserialize the transaction. var commitTx wire.MsgTx chainTxRow, err := db.FetchChainTxByID(ctx, commit.ChainTxnID) if err != nil { - return noneRootCommit, noneChainInfo, fmt.Errorf("failed to "+ - "fetch chain tx %d for commit %d: %w", - commit.ChainTxnID, commitID.Int64, err) + return nil, fmt.Errorf("failed to fetch chain tx %d for "+ + "commit %d: %w", commit.ChainTxnID, commit.CommitID, + err) } err = commitTx.Deserialize(bytes.NewReader(chainTxRow.RawTx)) if err != nil { - return noneRootCommit, noneChainInfo, fmt.Errorf("failed to "+ - "deserialize commit tx for commit %d: %w", - commitID.Int64, err) + return nil, fmt.Errorf("failed to deserialize commit tx for "+ + "commit %d: %w", commit.CommitID, err) } // Construct the SMT root node from the stored hash and sum. If they are // NULL (e.g., initial commit before ApplyStateTransition ran), use the // empty root. var rootNode *mssmt.BranchNode - if commit.SupplyRootHash == nil || !commit.SupplyRootSum.Valid { + if len(commit.SupplyRootHash) == 0 || !commit.SupplyRootSum.Valid { log.Warnf("fetchCommitment: Supply root hash/sum is NULL for "+ - "commit %d, using empty root", commitID.Int64) + "commit %d, using empty root", commit.CommitID) rootNode = mssmt.NewComputedBranch(mssmt.EmptyTreeRootHash, 0) } else { var rootHash mssmt.NodeHash @@ -1081,7 +1180,7 @@ func fetchCommitment(ctx context.Context, db SupplyCommitStore, rootNode = mssmt.NewComputedBranch(rootHash, rootSum) } - rootCommitment := supplycommit.RootCommitment{ + rootCommitment := &supplycommit.RootCommitment{ Txn: &commitTx, TxOutIdx: uint32(commit.OutputIndex.Int32), InternalKey: internalKey, @@ -1089,9 +1188,6 @@ func fetchCommitment(ctx context.Context, db SupplyCommitStore, SupplyRoot: rootNode, } - // Now, attempt to construct the chain info if confirmed. - var chainInfoOpt lfn.Option[commitmentChainInfo] - // If we have a valid block height, then that means that the block // header and/or merkle proof may also be present. if commit.BlockHeight.Valid { @@ -1107,7 +1203,7 @@ func fetchCommitment(ctx context.Context, db SupplyCommitStore, // Log error but don't fail the whole fetch log.Errorf("fetchCommitment: failed to "+ "deserialize block header "+ - "for commit %d: %v", commitID.Int64, + "for commit %d: %v", commit.CommitID, err) blockHeader = nil } @@ -1122,26 +1218,30 @@ func fetchCommitment(ctx context.Context, db SupplyCommitStore, if err != nil { log.Errorf("fetchCommitment: failed to "+ "decode merkle proof for commit %d: "+ - "%v", commitID.Int64, err) + "%v", commit.CommitID, err) merkleProof = nil } } if blockHeader != nil && merkleProof != nil { - chainInfoOpt = lfn.Some(commitmentChainInfo{ - BlockHeader: blockHeader, - MerkleProof: merkleProof, - BlockHeight: blockHeight, - }) + rootCommitment.CommitmentBlock = fn.Some( + supplycommit.CommitmentBlock{ + Height: blockHeight, + Hash: blockHeader.BlockHash(), + TxIndex: uint32(txIndex.Int32), + BlockHeader: blockHeader, + MerkleProof: merkleProof, + }, + ) } else { log.Warnf("fetchCommitment: commit %d has block "+ "height but missing header (%v) or proof (%v)", - commitID.Int64, blockHeader == nil, + commit.CommitID, blockHeader == nil, merkleProof == nil) } } - return lfn.Some(rootCommitment), chainInfoOpt, nil + return rootCommitment, nil } // FetchState attempts to fetch the state of the state machine for the @@ -1237,37 +1337,44 @@ func (s *SupplyCommitMachine) FetchState(ctx context.Context, // Next, we'll fetch the old and new commitments. If this is the // very first state transition, there won't be an old // commitment. - oldCommitmentOpt, _, err = fetchCommitment( - ctx, db, dbTransition.OldCommitmentID, groupKeyBytes, + oldCommitmentOpt, err = fetchCommitment( + ctx, db, dbTransition.OldCommitmentID, ) if err != nil { return fmt.Errorf("failed fetching old "+ "commitment: %w", err) } - newCommitmentOpt, newCommitChainInfoOpt, err := fetchCommitment( - ctx, db, dbTransition.NewCommitmentID, groupKeyBytes, + newCommitmentOpt, err := fetchCommitment( + ctx, db, dbTransition.NewCommitmentID, ) if err != nil { return fmt.Errorf("failed fetching new "+ "commitment: %w", err) } - // Construct the ChainProof if the new commitment's chain info - // is present. - newCommitChainInfoOpt.WhenSome(func(info commitmentChainInfo) { - if info.BlockHeader != nil && info.MerkleProof != nil { - chainProofOpt = lfn.Some(supplycommit.ChainProof{ //nolint:lll - Header: *info.BlockHeader, - BlockHeight: info.BlockHeight, - MerkleProof: *info.MerkleProof, - }) - } - }) - newCommit = newCommitmentOpt.UnwrapOr( supplycommit.RootCommitment{}, ) + newCommit.CommitmentBlock.WhenSome( + func(b supplycommit.CommitmentBlock) { + if b.BlockHeader == nil || + b.MerkleProof == nil { + + return + } + + chainProofOpt = lfn.Some( + supplycommit.ChainProof{ + Header: *b.BlockHeader, + BlockHeight: b.Height, + MerkleProof: *b.MerkleProof, + TxIndex: b.TxIndex, + }, + ) + }, + ) + return nil }) if err != nil { @@ -1507,7 +1614,8 @@ func (s *SupplyCommitMachine) ApplyStateTransition( GroupKey: groupKeyBytes, StateName: sqlStr(defaultStateName), LatestCommitmentID: dbTransition.NewCommitmentID, //nolint:lll - }) + }, + ) if err != nil { return fmt.Errorf("failed to update state machine to "+ "default: %w", err) diff --git a/tapdb/supply_commit_test.go b/tapdb/supply_commit_test.go index 88b2b6753..f9f821839 100644 --- a/tapdb/supply_commit_test.go +++ b/tapdb/supply_commit_test.go @@ -607,27 +607,30 @@ func (h *supplyCommitTestHarness) fetchCommitmentByID( var commitment sqlc.SupplyCommitment readTx := ReadTxOption() - err := h.commitMachine.db.ExecTx(h.ctx, readTx, - func(db SupplyCommitStore) error { - var txErr error - commitment, txErr = db.QuerySupplyCommitment( - h.ctx, commitID, - ) - return txErr + err := h.commitMachine.db.ExecTx( + h.ctx, readTx, func(db SupplyCommitStore) error { + row, err := db.QuerySupplyCommitment(h.ctx, commitID) + if err != nil { + return err + } + + commitment = row.SupplyCommitment + + return nil }, ) return commitment, err } // fetchInternalKeyByID fetches an internal key by ID directly via SQL. -// -//nolint:lll -func (h *supplyCommitTestHarness) fetchInternalKeyByID(keyID int64) FetchInternalKeyByIDRow { +func (h *supplyCommitTestHarness) fetchInternalKeyByID( + keyID int64) FetchInternalKeyByIDRow { + h.t.Helper() var keyRow FetchInternalKeyByIDRow readTx := ReadTxOption() - err := h.commitMachine.db.ExecTx(h.ctx, readTx, - func(db SupplyCommitStore) error { + err := h.commitMachine.db.ExecTx( + h.ctx, readTx, func(db SupplyCommitStore) error { var txErr error keyRow, txErr = db.FetchInternalKeyByID(h.ctx, keyID) return txErr @@ -638,13 +641,13 @@ func (h *supplyCommitTestHarness) fetchInternalKeyByID(keyID int64) FetchInterna } // fetchChainTxByID fetches a chain tx by ID directly via SQL. -func (h *supplyCommitTestHarness) fetchChainTxByID(txID int64, -) (FetchChainTxByIDRow, error) { +func (h *supplyCommitTestHarness) fetchChainTxByID( + txID int64) (FetchChainTxByIDRow, error) { var chainTx FetchChainTxByIDRow readTx := ReadTxOption() - err := h.commitMachine.db.ExecTx(h.ctx, readTx, - func(db SupplyCommitStore) error { + err := h.commitMachine.db.ExecTx( + h.ctx, readTx, func(db SupplyCommitStore) error { var txErr error chainTx, txErr = db.FetchChainTxByID(h.ctx, txID) return txErr @@ -1945,17 +1948,7 @@ func TestSupplyCommitMachineFetch(t *testing.T) { require.False(t, commitOpt.IsNone()) // Fetch the commitment details directly for comparison. - var dbCommit sqlc.SupplyCommitment - readTx := ReadTxOption() - err = h.commitMachine.db.ExecTx( - h.ctx, readTx, func(dbtx SupplyCommitStore) error { - var txErr error - dbCommit, txErr = dbtx.QuerySupplyCommitment( - h.ctx, commitID1, - ) - return txErr - }, - ) + dbCommit, err := h.fetchCommitmentByID(commitID1) require.NoError(t, err) // We'll now assert that the populated commitment we just read matches diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index d75ffdc51..405413678 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -270,6 +270,14 @@ type CommitmentBlock struct { // the block. TxIndex uint32 + // BlockHeader is the block header of the block that contains the + // commitment. + BlockHeader *wire.BlockHeader + + // MerkleProof is the merkle proof that proves that the supply + // commitment transaction is included in the block. + MerkleProof *proof.TxMerkleProof + // ChainFees is the amount in sats paid in on-chain fees for the // supply commitment transaction. ChainFees int64 @@ -533,9 +541,8 @@ type StateMachineStore interface { // error will be returned. // // TODO(roasbeef): also have it return the next event if exists? - FetchState(context.Context, asset.Specifier) ( - State, lfn.Option[SupplyStateTransition], error, - ) + FetchState(context.Context, asset.Specifier) (State, + lfn.Option[SupplyStateTransition], error) // ApplyStateTransition is used to apply a new state transition to the // target state machine. Once the transition has been applied, the state diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go index c168ea3ad..53a32574a 100644 --- a/universe/supplyverifier/env.go +++ b/universe/supplyverifier/env.go @@ -4,12 +4,19 @@ import ( "context" "fmt" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe/supplycommit" lfn "github.com/lightningnetwork/lnd/fn/v2" ) +var ( + // ErrCommitmentNotFound is returned when a supply commitment is not + // found. + ErrCommitmentNotFound = fmt.Errorf("commitment not found") +) + // SupplyCommitView is an interface that is used to look up supply commitments // and pre-commitments. type SupplyCommitView interface { @@ -22,6 +29,27 @@ type SupplyCommitView interface { // spec. SupplyCommit(ctx context.Context, assetSpec asset.Specifier) supplycommit.RootCommitResp + + // FetchCommitmentByOutpoint fetches a supply commitment by its outpoint + // and group key. If no commitment is found, it returns + // ErrCommitmentNotFound. + FetchCommitmentByOutpoint(ctx context.Context, + assetSpec asset.Specifier, + outpoint wire.OutPoint) (*supplycommit.RootCommitment, error) + + // FetchCommitmentBySpentOutpoint fetches a supply commitment by the + // outpoint it spent and group key. If no commitment is found, it + // returns ErrCommitmentNotFound. + FetchCommitmentBySpentOutpoint(ctx context.Context, + assetSpec asset.Specifier, + spentOutpoint wire.OutPoint) (*supplycommit.RootCommitment, + error) + + // FetchStartingCommitment fetches the very first supply commitment of + // an asset group. If no commitment is found, it returns + // ErrCommitmentNotFound. + FetchStartingCommitment(ctx context.Context, + assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) } // Environment is a struct that holds all the dependencies that the supply From a4790066e0a7817bba1f347b69a52de8a0d729ff Mon Sep 17 00:00:00 2001 From: ffranr Date: Thu, 21 Aug 2025 11:16:48 +0100 Subject: [PATCH 20/51] rpcserver: set previously added BlockHeader and MerkleProof fields These fields in CommitmentBlock were introduced in an earlier commit. Populating them here allows simplifying the Manager.InsertSupplyCommit method interface in the supplyverifier package. --- rpcserver.go | 44 ++++++++++++++---------------- universe/supplyverifier/manager.go | 3 +- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 907fa6877..1a5064ce1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4642,34 +4642,32 @@ func marshalSupplyUpdateEvent( // both a supplycommit.RootCommitment and supplycommit.ChainProof. func unmarshalSupplyCommitChainData( rpcData *unirpc.SupplyCommitChainData) (*supplycommit.RootCommitment, - *supplycommit.ChainProof, error) { + error) { if rpcData == nil { - return nil, nil, fmt.Errorf("supply commit chain data is nil") + return nil, fmt.Errorf("supply commit chain data is nil") } var txn wire.MsgTx err := txn.Deserialize(bytes.NewReader(rpcData.Txn)) if err != nil { - return nil, nil, fmt.Errorf("unable to deserialize "+ - "transaction: %w", err) + return nil, fmt.Errorf("unable to deserialize transaction: %w", + err) } internalKey, err := btcec.ParsePubKey(rpcData.InternalKey) if err != nil { - return nil, nil, fmt.Errorf("unable to parse internal key: %w", - err) + return nil, fmt.Errorf("unable to parse internal key: %w", err) } outputKey, err := btcec.ParsePubKey(rpcData.OutputKey) if err != nil { - return nil, nil, fmt.Errorf("unable to parse output key: %w", - err) + return nil, fmt.Errorf("unable to parse output key: %w", err) } // Convert supply root hash. if len(rpcData.SupplyRootHash) != 32 { - return nil, nil, fmt.Errorf("invalid supply root hash size: "+ + return nil, fmt.Errorf("invalid supply root hash size: "+ "expected %d, got %d", 32, len(rpcData.SupplyRootHash)) } var supplyRootHash mssmt.NodeHash @@ -4679,7 +4677,7 @@ func unmarshalSupplyCommitChainData( var commitmentBlock fn.Option[supplycommit.CommitmentBlock] if len(rpcData.BlockHash) > 0 { if len(rpcData.BlockHash) != chainhash.HashSize { - return nil, nil, fmt.Errorf("invalid block hash size: "+ + return nil, fmt.Errorf("invalid block hash size: "+ "expected %d, got %d", chainhash.HashSize, len(rpcData.BlockHash)) } @@ -4709,25 +4707,25 @@ func unmarshalSupplyCommitChainData( var blockHeader wire.BlockHeader err = blockHeader.Deserialize(bytes.NewReader(rpcData.BlockHeader)) if err != nil { - return nil, nil, fmt.Errorf("unable to deserialize block "+ - "header: %w", err) + return nil, fmt.Errorf("unable to deserialize block header: "+ + "%w", err) } var merkleProof proof.TxMerkleProof err = merkleProof.Decode(bytes.NewReader(rpcData.TxBlockMerkleProof)) if err != nil { - return nil, nil, fmt.Errorf("unable to decode merkle proof: %w", - err) + return nil, fmt.Errorf("unable to decode merkle proof: %w", err) } - chainProof := &supplycommit.ChainProof{ - Header: blockHeader, - BlockHeight: rpcData.BlockHeight, - MerkleProof: merkleProof, + rootCommitment.CommitmentBlock = fn.Some(supplycommit.CommitmentBlock{ + Height: rpcData.BlockHeight, + Hash: blockHeader.BlockHash(), TxIndex: rpcData.TxIndex, - } + BlockHeader: &blockHeader, + MerkleProof: &merkleProof, + }) - return rootCommitment, chainProof, nil + return rootCommitment, nil } // unmarshalSupplyLeaves converts the RPC supply leaves into a SupplyLeaves @@ -4785,9 +4783,7 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, groupPubKey.SerializeCompressed()) // Unmarshal the supply commit chain data. - rootCommitment, chainProof, err := unmarshalSupplyCommitChainData( - req.ChainData, - ) + rootCommitment, err := unmarshalSupplyCommitChainData(req.ChainData) if err != nil { return nil, fmt.Errorf("failed to unmarshal chain data: %w", err) @@ -4809,7 +4805,7 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) err = r.cfg.SupplyVerifyManager.InsertSupplyCommit( - ctx, assetSpec, *rootCommitment, *supplyLeaves, *chainProof, + ctx, assetSpec, *rootCommitment, *supplyLeaves, ) if err != nil { return nil, fmt.Errorf("failed to insert supply commitment: %w", diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index b3abba6e9..d9f3f2014 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -209,8 +209,7 @@ func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, // group in the node's local database. func (m *Manager) InsertSupplyCommit(ctx context.Context, assetSpec asset.Specifier, commitment supplycommit.RootCommitment, - leaves supplycommit.SupplyLeaves, - chainProof supplycommit.ChainProof) error { + leaves supplycommit.SupplyLeaves) error { // TODO(ffranr): Verify supply commit without starting a state machine. // This is effectively where universe server supply commit verification From b5e5642b3e6e334ed05a65bef6a5001d5c9f89f3 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 15 Aug 2025 15:42:47 +0200 Subject: [PATCH 21/51] tapdb+universe: add SpentCommitment to RootCommitment To find out what commitment is being spent by a next one, we add the optional SpentCommitment field to the RootCommitment struct. The field should only be None for the very first commitment of an asset group. --- tapdb/sqlc/querier.go | 1 + tapdb/sqlc/queries/supply_commit.sql | 7 +++++++ tapdb/sqlc/supply_commit.sql.go | 20 +++++++++++++++++++ tapdb/supply_commit.go | 30 ++++++++++++++++++++++++++++ universe/supplycommit/env.go | 5 +++++ universe/supplycommit/transitions.go | 14 ++++++++----- 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 7547003cd..c9cc85d3b 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -193,6 +193,7 @@ type Querier interface { QuerySupplyCommitment(ctx context.Context, commitID int64) (QuerySupplyCommitmentRow, error) QuerySupplyCommitmentByOutpoint(ctx context.Context, arg QuerySupplyCommitmentByOutpointParams) (QuerySupplyCommitmentByOutpointRow, error) QuerySupplyCommitmentBySpentOutpoint(ctx context.Context, arg QuerySupplyCommitmentBySpentOutpointParams) (QuerySupplyCommitmentBySpentOutpointRow, error) + QuerySupplyCommitmentOutpoint(ctx context.Context, commitID int64) (QuerySupplyCommitmentOutpointRow, error) QuerySupplyLeavesByHeight(ctx context.Context, arg QuerySupplyLeavesByHeightParams) ([]QuerySupplyLeavesByHeightRow, error) QuerySupplyUpdateEvents(ctx context.Context, transitionID sql.NullInt64) ([]QuerySupplyUpdateEventsRow, error) // TODO(roasbeef): use the universe id instead for the grouping? so namespace diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 066c0b8e6..8cccddc73 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -173,6 +173,13 @@ FROM supply_commitments AS sc WHERE sc.spent_commitment IS NULL AND sc.group_key = @group_key; +-- name: QuerySupplyCommitmentOutpoint :one +SELECT ct.txid, sc.output_index +FROM supply_commitments AS sc + JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.commit_id = @commit_id; + -- name: UpdateSupplyCommitTransitionCommitment :exec UPDATE supply_commit_transitions SET new_commitment_id = @new_commitment_id, diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index 745383f11..d33ae0104 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -620,6 +620,26 @@ func (q *Queries) QuerySupplyCommitmentBySpentOutpoint(ctx context.Context, arg return i, err } +const QuerySupplyCommitmentOutpoint = `-- name: QuerySupplyCommitmentOutpoint :one +SELECT ct.txid, sc.output_index +FROM supply_commitments AS sc + JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.commit_id = $1 +` + +type QuerySupplyCommitmentOutpointRow struct { + Txid []byte + OutputIndex sql.NullInt32 +} + +func (q *Queries) QuerySupplyCommitmentOutpoint(ctx context.Context, commitID int64) (QuerySupplyCommitmentOutpointRow, error) { + row := q.db.QueryRowContext(ctx, QuerySupplyCommitmentOutpoint, commitID) + var i QuerySupplyCommitmentOutpointRow + err := row.Scan(&i.Txid, &i.OutputIndex) + return i, err +} + const QuerySupplyUpdateEvents = `-- name: QuerySupplyUpdateEvents :many SELECT ue.event_id, diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 25e8eac14..49a9f15ab 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -10,6 +10,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" @@ -189,6 +190,11 @@ type SupplyCommitStore interface { QueryStartingSupplyCommitment(ctx context.Context, groupKey []byte) (sqlc.QueryStartingSupplyCommitmentRow, error) + // QuerySupplyCommitmentOutpoint fetches the outpoint of a supply + // commitment by its ID. + QuerySupplyCommitmentOutpoint(ctx context.Context, + commitID int64) (sqlc.QuerySupplyCommitmentOutpointRow, error) + // FetchChainTx fetches a chain transaction by its TXID. FetchChainTx(ctx context.Context, txid []byte) (ChainTxn, error) @@ -1241,6 +1247,30 @@ func parseSupplyCommitmentRow(ctx context.Context, commit SupplyCommitment, } } + if commit.SpentCommitment.Valid { + spentRow, err := db.QuerySupplyCommitmentOutpoint( + ctx, commit.SpentCommitment.Int64, + ) + if err != nil { + return nil, fmt.Errorf("failed to query spent "+ + "commitment with ID %d for commit %d: %w", + commit.SpentCommitment.Int64, commit.CommitID, + err) + } + + hash, err := chainhash.NewHash(spentRow.Txid) + if err != nil { + return nil, fmt.Errorf("failed to parse spent "+ + "commitment txid %x for commit %d: %w", + spentRow.Txid, commit.CommitID, err) + } + + rootCommitment.SpentCommitment = fn.Some(wire.OutPoint{ + Hash: *hash, + Index: uint32(spentRow.OutputIndex.Int32), + }) + } + return rootCommitment, nil } diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 405413678..96a3eeec5 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -309,6 +309,11 @@ type RootCommitment struct { // asset supply. This may be None if the commitment has not yet // been mined. CommitmentBlock fn.Option[CommitmentBlock] + + // SpentCommitment is the outpoint of the previous root commitment that + // this root commitment is spending. This will be None if this is the + // first root commitment for the asset. + SpentCommitment fn.Option[wire.OutPoint] } // TxIn returns the transaction input that corresponds to the root commitment. diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index 110bf410e..a9eabef27 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -482,6 +482,7 @@ func newRootCommitment(ctx context.Context, // as an input to the new transaction. Pre-commitments are only present // on mint transactions where as the old commitment is the last // commitment that was broadcast. + var spentCommitOp fn.Option[wire.OutPoint] oldCommitment.WhenSome(func(r RootCommitment) { logger.WhenSome(func(l btclog.Logger) { l.Infof("Re-using prior commitment as outpoint=%v: %v", @@ -510,6 +511,8 @@ func newRootCommitment(ctx context.Context, TaprootInternalKey: trBip32Derivation.XOnlyPubKey, TaprootMerkleRoot: commitTapscriptRoot, }) + + spentCommitOp = fn.Some(r.CommitPoint()) }) // TODO(roasbef): do CreateTaprootSignature instead? @@ -581,11 +584,12 @@ func newRootCommitment(ctx context.Context, // // TODO(roasbeef): use diff internal key? newSupplyCommit := RootCommitment{ - Txn: newCommitTx, - TxOutIdx: 0, - InternalKey: commitInternalKey, - OutputKey: tapOutKey, - SupplyRoot: newSupplyRoot, + Txn: newCommitTx, + TxOutIdx: 0, + InternalKey: commitInternalKey, + OutputKey: tapOutKey, + SupplyRoot: newSupplyRoot, + SpentCommitment: spentCommitOp, } logger.WhenSome(func(l btclog.Logger) { From 8897e41ed565bcf703a59c1b8943614ef7eb48fd Mon Sep 17 00:00:00 2001 From: ffranr Date: Thu, 21 Aug 2025 11:48:09 +0100 Subject: [PATCH 22/51] rpc: add field spent_commitment_outpoint to InsertSupplyCommitRequest This field will allow us to directly find the previous supply commitment which will be useful during verification. We will make use of this data in a subsiquent commit. --- rpcserver.go | 12 + taprpc/universerpc/universe.pb.go | 512 ++++++++++++----------- taprpc/universerpc/universe.proto | 11 +- taprpc/universerpc/universe.swagger.json | 20 + 4 files changed, 305 insertions(+), 250 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 1a5064ce1..c1f9e4244 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4789,6 +4789,18 @@ func (r *rpcServer) InsertSupplyCommit(ctx context.Context, err) } + if req.SpentCommitmentOutpoint != nil { + op, err := rpcutils.UnmarshalOutPoint( + req.SpentCommitmentOutpoint, + ) + if err != nil { + return nil, fmt.Errorf("failed to parse spent "+ + "commitment outpoint: %w", err) + } + + rootCommitment.SpentCommitment = fn.Some(op) + } + supplyLeaves, err := unmarshalSupplyLeaves( req.IssuanceLeaves, req.BurnLeaves, req.IgnoreLeaves, ) diff --git a/taprpc/universerpc/universe.pb.go b/taprpc/universerpc/universe.pb.go index 5a386cf02..eef2ba9f6 100644 --- a/taprpc/universerpc/universe.pb.go +++ b/taprpc/universerpc/universe.pb.go @@ -4501,10 +4501,14 @@ type InsertSupplyCommitRequest struct { // The supply commitment chain data that contains both the commitment and // chain proof information. ChainData *SupplyCommitChainData `protobuf:"bytes,3,opt,name=chain_data,json=chainData,proto3" json:"chain_data,omitempty"` + // The outpoint of the previous commitment that this new commitment is + // spending. This must be set unless this is the very first supply + // commitment of a grouped asset. + SpentCommitmentOutpoint *taprpc.OutPoint `protobuf:"bytes,4,opt,name=spent_commitment_outpoint,json=spentCommitmentOutpoint,proto3" json:"spent_commitment_outpoint,omitempty"` // The supply leaves that represent the supply changes for the asset group. - IssuanceLeaves []*SupplyLeafEntry `protobuf:"bytes,4,rep,name=issuance_leaves,json=issuanceLeaves,proto3" json:"issuance_leaves,omitempty"` - BurnLeaves []*SupplyLeafEntry `protobuf:"bytes,5,rep,name=burn_leaves,json=burnLeaves,proto3" json:"burn_leaves,omitempty"` - IgnoreLeaves []*SupplyLeafEntry `protobuf:"bytes,6,rep,name=ignore_leaves,json=ignoreLeaves,proto3" json:"ignore_leaves,omitempty"` + IssuanceLeaves []*SupplyLeafEntry `protobuf:"bytes,5,rep,name=issuance_leaves,json=issuanceLeaves,proto3" json:"issuance_leaves,omitempty"` + BurnLeaves []*SupplyLeafEntry `protobuf:"bytes,6,rep,name=burn_leaves,json=burnLeaves,proto3" json:"burn_leaves,omitempty"` + IgnoreLeaves []*SupplyLeafEntry `protobuf:"bytes,7,rep,name=ignore_leaves,json=ignoreLeaves,proto3" json:"ignore_leaves,omitempty"` } func (x *InsertSupplyCommitRequest) Reset() { @@ -4567,6 +4571,13 @@ func (x *InsertSupplyCommitRequest) GetChainData() *SupplyCommitChainData { return nil } +func (x *InsertSupplyCommitRequest) GetSpentCommitmentOutpoint() *taprpc.OutPoint { + if x != nil { + return x.SpentCommitmentOutpoint + } + return nil +} + func (x *InsertSupplyCommitRequest) GetIssuanceLeaves() []*SupplyLeafEntry { if x != nil { return x.IssuanceLeaves @@ -5242,7 +5253,7 @@ var file_universerpc_universe_proto_rawDesc = []byte{ 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x22, 0x84, 0x03, 0x0a, 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, + 0x65, 0x78, 0x22, 0xd2, 0x03, 0x0a, 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, 0x6f, @@ -5253,195 +5264,200 @@ var file_universerpc_universe_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, - 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, - 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, - 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, - 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, - 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, - 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, 0x09, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, 0x73, - 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, 0x66, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, - 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, - 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, - 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x79, - 0x6e, 0x63, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, - 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0d, - 0x0a, 0x09, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, 0x01, - 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, - 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, - 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, - 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, - 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, 0x02, + 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x19, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x17, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, + 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, + 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, + 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, 0x72, 0x6e, + 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x75, 0x72, + 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, + 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, + 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, + 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, + 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x53, 0x53, + 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x02, + 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x79, 0x6e, 0x63, + 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x53, 0x53, + 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, + 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, 0x01, 0x0a, 0x0e, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x10, + 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, - 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, 0x10, - 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, - 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, - 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x48, - 0x45, 0x49, 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, - 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, 0x10, - 0x07, 0x2a, 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, - 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, - 0x43, 0x10, 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, - 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, - 0x13, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, - 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, - 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x42, - 0x4c, 0x45, 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, - 0x6f, 0x6f, 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, + 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, 0x52, 0x54, + 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, 0x02, 0x12, 0x16, + 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, + 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, 0x10, 0x04, 0x12, + 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, + 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x52, + 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x48, 0x45, 0x49, + 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, + 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, 0x10, 0x07, 0x2a, + 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, + 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x10, + 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, + 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x46, + 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, + 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, + 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x42, 0x4c, 0x45, + 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, + 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, + 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, - 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x6f, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, + 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, - 0x73, 0x12, 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x44, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, - 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x49, - 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x6f, 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, - 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3b, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, - 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, - 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x16, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x46, 0x0a, 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, - 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, - 0x13, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, - 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, - 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, - 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, - 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, - 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, - 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, - 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, - 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, - 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, - 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, + 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, + 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x44, + 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x18, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x49, 0x6e, 0x73, + 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, + 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, + 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, + 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, + 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x53, + 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x16, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, + 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x19, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, 0x65, 0x74, + 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, + 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, + 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x49, + 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, + 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, + 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, + 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, + 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, + 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -5537,6 +5553,7 @@ var file_universerpc_universe_proto_goTypes = []any{ (*taprpc.GroupKeyReveal)(nil), // 75: taprpc.GroupKeyReveal (taprpc.AssetType)(0), // 76: taprpc.AssetType (*taprpc.AssetOutPoint)(nil), // 77: taprpc.AssetOutPoint + (*taprpc.OutPoint)(nil), // 78: taprpc.OutPoint } var file_universerpc_universe_proto_depIdxs = []int32{ 0, // 0: universerpc.MultiverseRootRequest.proof_type:type_name -> universerpc.ProofType @@ -5612,63 +5629,64 @@ var file_universerpc_universe_proto_depIdxs = []int32{ 65, // 70: universerpc.FetchSupplyLeavesResponse.burn_leaves:type_name -> universerpc.SupplyLeafEntry 65, // 71: universerpc.FetchSupplyLeavesResponse.ignore_leaves:type_name -> universerpc.SupplyLeafEntry 67, // 72: universerpc.InsertSupplyCommitRequest.chain_data:type_name -> universerpc.SupplyCommitChainData - 65, // 73: universerpc.InsertSupplyCommitRequest.issuance_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 74: universerpc.InsertSupplyCommitRequest.burn_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 75: universerpc.InsertSupplyCommitRequest.ignore_leaves:type_name -> universerpc.SupplyLeafEntry - 10, // 76: universerpc.AssetRootResponse.UniverseRootsEntry.value:type_name -> universerpc.UniverseRoot - 5, // 77: universerpc.Universe.MultiverseRoot:input_type -> universerpc.MultiverseRootRequest - 7, // 78: universerpc.Universe.AssetRoots:input_type -> universerpc.AssetRootRequest - 12, // 79: universerpc.Universe.QueryAssetRoots:input_type -> universerpc.AssetRootQuery - 14, // 80: universerpc.Universe.DeleteAssetRoot:input_type -> universerpc.DeleteRootQuery - 18, // 81: universerpc.Universe.AssetLeafKeys:input_type -> universerpc.AssetLeafKeysRequest - 9, // 82: universerpc.Universe.AssetLeaves:input_type -> universerpc.ID - 22, // 83: universerpc.Universe.QueryProof:input_type -> universerpc.UniverseKey - 25, // 84: universerpc.Universe.InsertProof:input_type -> universerpc.AssetProof - 26, // 85: universerpc.Universe.PushProof:input_type -> universerpc.PushProofRequest - 28, // 86: universerpc.Universe.Info:input_type -> universerpc.InfoRequest - 31, // 87: universerpc.Universe.SyncUniverse:input_type -> universerpc.SyncRequest - 36, // 88: universerpc.Universe.ListFederationServers:input_type -> universerpc.ListFederationServersRequest - 38, // 89: universerpc.Universe.AddFederationServer:input_type -> universerpc.AddFederationServerRequest - 40, // 90: universerpc.Universe.DeleteFederationServer:input_type -> universerpc.DeleteFederationServerRequest - 33, // 91: universerpc.Universe.UniverseStats:input_type -> universerpc.StatsRequest - 43, // 92: universerpc.Universe.QueryAssetStats:input_type -> universerpc.AssetStatsQuery - 47, // 93: universerpc.Universe.QueryEvents:input_type -> universerpc.QueryEventsRequest - 50, // 94: universerpc.Universe.SetFederationSyncConfig:input_type -> universerpc.SetFederationSyncConfigRequest - 54, // 95: universerpc.Universe.QueryFederationSyncConfig:input_type -> universerpc.QueryFederationSyncConfigRequest - 56, // 96: universerpc.Universe.IgnoreAssetOutPoint:input_type -> universerpc.IgnoreAssetOutPointRequest - 58, // 97: universerpc.Universe.UpdateSupplyCommit:input_type -> universerpc.UpdateSupplyCommitRequest - 60, // 98: universerpc.Universe.FetchSupplyCommit:input_type -> universerpc.FetchSupplyCommitRequest - 63, // 99: universerpc.Universe.FetchSupplyLeaves:input_type -> universerpc.FetchSupplyLeavesRequest - 68, // 100: universerpc.Universe.InsertSupplyCommit:input_type -> universerpc.InsertSupplyCommitRequest - 6, // 101: universerpc.Universe.MultiverseRoot:output_type -> universerpc.MultiverseRootResponse - 11, // 102: universerpc.Universe.AssetRoots:output_type -> universerpc.AssetRootResponse - 13, // 103: universerpc.Universe.QueryAssetRoots:output_type -> universerpc.QueryRootResponse - 15, // 104: universerpc.Universe.DeleteAssetRoot:output_type -> universerpc.DeleteRootResponse - 19, // 105: universerpc.Universe.AssetLeafKeys:output_type -> universerpc.AssetLeafKeyResponse - 21, // 106: universerpc.Universe.AssetLeaves:output_type -> universerpc.AssetLeafResponse - 23, // 107: universerpc.Universe.QueryProof:output_type -> universerpc.AssetProofResponse - 23, // 108: universerpc.Universe.InsertProof:output_type -> universerpc.AssetProofResponse - 27, // 109: universerpc.Universe.PushProof:output_type -> universerpc.PushProofResponse - 29, // 110: universerpc.Universe.Info:output_type -> universerpc.InfoResponse - 34, // 111: universerpc.Universe.SyncUniverse:output_type -> universerpc.SyncResponse - 37, // 112: universerpc.Universe.ListFederationServers:output_type -> universerpc.ListFederationServersResponse - 39, // 113: universerpc.Universe.AddFederationServer:output_type -> universerpc.AddFederationServerResponse - 41, // 114: universerpc.Universe.DeleteFederationServer:output_type -> universerpc.DeleteFederationServerResponse - 42, // 115: universerpc.Universe.UniverseStats:output_type -> universerpc.StatsResponse - 46, // 116: universerpc.Universe.QueryAssetStats:output_type -> universerpc.UniverseAssetStats - 48, // 117: universerpc.Universe.QueryEvents:output_type -> universerpc.QueryEventsResponse - 51, // 118: universerpc.Universe.SetFederationSyncConfig:output_type -> universerpc.SetFederationSyncConfigResponse - 55, // 119: universerpc.Universe.QueryFederationSyncConfig:output_type -> universerpc.QueryFederationSyncConfigResponse - 57, // 120: universerpc.Universe.IgnoreAssetOutPoint:output_type -> universerpc.IgnoreAssetOutPointResponse - 59, // 121: universerpc.Universe.UpdateSupplyCommit:output_type -> universerpc.UpdateSupplyCommitResponse - 62, // 122: universerpc.Universe.FetchSupplyCommit:output_type -> universerpc.FetchSupplyCommitResponse - 66, // 123: universerpc.Universe.FetchSupplyLeaves:output_type -> universerpc.FetchSupplyLeavesResponse - 69, // 124: universerpc.Universe.InsertSupplyCommit:output_type -> universerpc.InsertSupplyCommitResponse - 101, // [101:125] is the sub-list for method output_type - 77, // [77:101] is the sub-list for method input_type - 77, // [77:77] is the sub-list for extension type_name - 77, // [77:77] is the sub-list for extension extendee - 0, // [0:77] is the sub-list for field type_name + 78, // 73: universerpc.InsertSupplyCommitRequest.spent_commitment_outpoint:type_name -> taprpc.OutPoint + 65, // 74: universerpc.InsertSupplyCommitRequest.issuance_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 75: universerpc.InsertSupplyCommitRequest.burn_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 76: universerpc.InsertSupplyCommitRequest.ignore_leaves:type_name -> universerpc.SupplyLeafEntry + 10, // 77: universerpc.AssetRootResponse.UniverseRootsEntry.value:type_name -> universerpc.UniverseRoot + 5, // 78: universerpc.Universe.MultiverseRoot:input_type -> universerpc.MultiverseRootRequest + 7, // 79: universerpc.Universe.AssetRoots:input_type -> universerpc.AssetRootRequest + 12, // 80: universerpc.Universe.QueryAssetRoots:input_type -> universerpc.AssetRootQuery + 14, // 81: universerpc.Universe.DeleteAssetRoot:input_type -> universerpc.DeleteRootQuery + 18, // 82: universerpc.Universe.AssetLeafKeys:input_type -> universerpc.AssetLeafKeysRequest + 9, // 83: universerpc.Universe.AssetLeaves:input_type -> universerpc.ID + 22, // 84: universerpc.Universe.QueryProof:input_type -> universerpc.UniverseKey + 25, // 85: universerpc.Universe.InsertProof:input_type -> universerpc.AssetProof + 26, // 86: universerpc.Universe.PushProof:input_type -> universerpc.PushProofRequest + 28, // 87: universerpc.Universe.Info:input_type -> universerpc.InfoRequest + 31, // 88: universerpc.Universe.SyncUniverse:input_type -> universerpc.SyncRequest + 36, // 89: universerpc.Universe.ListFederationServers:input_type -> universerpc.ListFederationServersRequest + 38, // 90: universerpc.Universe.AddFederationServer:input_type -> universerpc.AddFederationServerRequest + 40, // 91: universerpc.Universe.DeleteFederationServer:input_type -> universerpc.DeleteFederationServerRequest + 33, // 92: universerpc.Universe.UniverseStats:input_type -> universerpc.StatsRequest + 43, // 93: universerpc.Universe.QueryAssetStats:input_type -> universerpc.AssetStatsQuery + 47, // 94: universerpc.Universe.QueryEvents:input_type -> universerpc.QueryEventsRequest + 50, // 95: universerpc.Universe.SetFederationSyncConfig:input_type -> universerpc.SetFederationSyncConfigRequest + 54, // 96: universerpc.Universe.QueryFederationSyncConfig:input_type -> universerpc.QueryFederationSyncConfigRequest + 56, // 97: universerpc.Universe.IgnoreAssetOutPoint:input_type -> universerpc.IgnoreAssetOutPointRequest + 58, // 98: universerpc.Universe.UpdateSupplyCommit:input_type -> universerpc.UpdateSupplyCommitRequest + 60, // 99: universerpc.Universe.FetchSupplyCommit:input_type -> universerpc.FetchSupplyCommitRequest + 63, // 100: universerpc.Universe.FetchSupplyLeaves:input_type -> universerpc.FetchSupplyLeavesRequest + 68, // 101: universerpc.Universe.InsertSupplyCommit:input_type -> universerpc.InsertSupplyCommitRequest + 6, // 102: universerpc.Universe.MultiverseRoot:output_type -> universerpc.MultiverseRootResponse + 11, // 103: universerpc.Universe.AssetRoots:output_type -> universerpc.AssetRootResponse + 13, // 104: universerpc.Universe.QueryAssetRoots:output_type -> universerpc.QueryRootResponse + 15, // 105: universerpc.Universe.DeleteAssetRoot:output_type -> universerpc.DeleteRootResponse + 19, // 106: universerpc.Universe.AssetLeafKeys:output_type -> universerpc.AssetLeafKeyResponse + 21, // 107: universerpc.Universe.AssetLeaves:output_type -> universerpc.AssetLeafResponse + 23, // 108: universerpc.Universe.QueryProof:output_type -> universerpc.AssetProofResponse + 23, // 109: universerpc.Universe.InsertProof:output_type -> universerpc.AssetProofResponse + 27, // 110: universerpc.Universe.PushProof:output_type -> universerpc.PushProofResponse + 29, // 111: universerpc.Universe.Info:output_type -> universerpc.InfoResponse + 34, // 112: universerpc.Universe.SyncUniverse:output_type -> universerpc.SyncResponse + 37, // 113: universerpc.Universe.ListFederationServers:output_type -> universerpc.ListFederationServersResponse + 39, // 114: universerpc.Universe.AddFederationServer:output_type -> universerpc.AddFederationServerResponse + 41, // 115: universerpc.Universe.DeleteFederationServer:output_type -> universerpc.DeleteFederationServerResponse + 42, // 116: universerpc.Universe.UniverseStats:output_type -> universerpc.StatsResponse + 46, // 117: universerpc.Universe.QueryAssetStats:output_type -> universerpc.UniverseAssetStats + 48, // 118: universerpc.Universe.QueryEvents:output_type -> universerpc.QueryEventsResponse + 51, // 119: universerpc.Universe.SetFederationSyncConfig:output_type -> universerpc.SetFederationSyncConfigResponse + 55, // 120: universerpc.Universe.QueryFederationSyncConfig:output_type -> universerpc.QueryFederationSyncConfigResponse + 57, // 121: universerpc.Universe.IgnoreAssetOutPoint:output_type -> universerpc.IgnoreAssetOutPointResponse + 59, // 122: universerpc.Universe.UpdateSupplyCommit:output_type -> universerpc.UpdateSupplyCommitResponse + 62, // 123: universerpc.Universe.FetchSupplyCommit:output_type -> universerpc.FetchSupplyCommitResponse + 66, // 124: universerpc.Universe.FetchSupplyLeaves:output_type -> universerpc.FetchSupplyLeavesResponse + 69, // 125: universerpc.Universe.InsertSupplyCommit:output_type -> universerpc.InsertSupplyCommitResponse + 102, // [102:126] is the sub-list for method output_type + 78, // [78:102] is the sub-list for method input_type + 78, // [78:78] is the sub-list for extension type_name + 78, // [78:78] is the sub-list for extension extendee + 0, // [0:78] is the sub-list for field type_name } func init() { file_universerpc_universe_proto_init() } diff --git a/taprpc/universerpc/universe.proto b/taprpc/universerpc/universe.proto index 0cf12c38c..d7236a825 100644 --- a/taprpc/universerpc/universe.proto +++ b/taprpc/universerpc/universe.proto @@ -1000,10 +1000,15 @@ message InsertSupplyCommitRequest { // chain proof information. SupplyCommitChainData chain_data = 3; + // The outpoint of the previous commitment that this new commitment is + // spending. This must be set unless this is the very first supply + // commitment of a grouped asset. + taprpc.OutPoint spent_commitment_outpoint = 4; + // The supply leaves that represent the supply changes for the asset group. - repeated SupplyLeafEntry issuance_leaves = 4; - repeated SupplyLeafEntry burn_leaves = 5; - repeated SupplyLeafEntry ignore_leaves = 6; + repeated SupplyLeafEntry issuance_leaves = 5; + repeated SupplyLeafEntry burn_leaves = 6; + repeated SupplyLeafEntry ignore_leaves = 7; } message InsertSupplyCommitResponse { diff --git a/taprpc/universerpc/universe.swagger.json b/taprpc/universerpc/universe.swagger.json index 386080922..adc1b6c23 100644 --- a/taprpc/universerpc/universe.swagger.json +++ b/taprpc/universerpc/universe.swagger.json @@ -1742,6 +1742,10 @@ "$ref": "#/definitions/universerpcSupplyCommitChainData", "description": "The supply commitment chain data that contains both the commitment and\nchain proof information." }, + "spent_commitment_outpoint": { + "$ref": "#/definitions/taprpcOutPoint", + "description": "The outpoint of the previous commitment that this new commitment is\nspending. This must be set unless this is the very first supply\ncommitment of a grouped asset." + }, "issuance_leaves": { "type": "array", "items": { @@ -2153,6 +2157,22 @@ } } }, + "taprpcOutPoint": { + "type": "object", + "properties": { + "txid": { + "type": "string", + "format": "byte", + "description": "Raw bytes representing the transaction id." + }, + "output_index": { + "type": "integer", + "format": "int64", + "description": "The index of the output on the transaction." + } + }, + "description": "Represents a Bitcoin transaction outpoint." + }, "taprpcPrevInputAsset": { "type": "object", "properties": { From 0165ccc4e468e108ab6b87a6316ccd4849616d8b Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 25 Aug 2025 13:50:52 +0100 Subject: [PATCH 23/51] taprootassets: add RpcSupplySync to bridge RPC and verifier syncer Introduce RpcSupplySync as an intermediary between the supply commit leaf fetch RPC endpoints and the supply verifier syncer. --- supplysync_rpc.go | 176 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 supplysync_rpc.go diff --git a/supplysync_rpc.go b/supplysync_rpc.go new file mode 100644 index 000000000..36babfbae --- /dev/null +++ b/supplysync_rpc.go @@ -0,0 +1,176 @@ +package taprootassets + +import ( + "bytes" + "context" + "fmt" + + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/taprpc" + unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightninglabs/taproot-assets/universe" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" +) + +// RpcSupplySync is an implementation of the universe.SupplySyncer interface +// that uses an RPC connection to target a remote universe server. +type RpcSupplySync struct { + // serverAddr is the address of the remote universe server. + serverAddr universe.ServerAddr + + // conn is the RPC connection to the remote universe server. + conn *universeClientConn +} + +// NewRpcSupplySync creates a new RpcSupplySync instance that dials out to +// the target remote universe server address. +func NewRpcSupplySync( + serverAddr universe.ServerAddr) (supplyverifier.UniverseClient, error) { + + conn, err := ConnectUniverse(serverAddr) + if err != nil { + return nil, fmt.Errorf("unable to connect to universe RPC "+ + "server: %w", err) + } + + return &RpcSupplySync{ + serverAddr: serverAddr, + conn: conn, + }, nil +} + +// Ensure NewRpcSupplySync is of type UniverseClientFactory. +var _ supplyverifier.UniverseClientFactory = NewRpcSupplySync + +// InsertSupplyCommit inserts a supply commitment for a specific asset +// group into the remote universe server. +func (r *RpcSupplySync) InsertSupplyCommit(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves, + chainProof supplycommit.ChainProof) error { + + srvrLog.Infof("[RpcSupplySync.InsertSupplyCommit]: inserting supply "+ + "commitment into remote server "+ + "(server_addr=%s, asset=%s, supply_tree_root_hash=%s)", + r.serverAddr.HostStr(), assetSpec.String(), + commitment.SupplyRoot.NodeHash().String()) + + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return fmt.Errorf("unable to unwrap group key: %w", err) + } + + // Marshal the supply commit chain data to RPC format. + rpcChainData, err := marshalSupplyCommitChainData( + commitment, chainProof, + ) + if err != nil { + return fmt.Errorf("unable to marshal chain data: %w", err) + } + + issuanceLeaves, burnLeaves, ignoreLeaves, err := marshalSupplyLeaves( + leaves, + ) + if err != nil { + return fmt.Errorf("unable to marshal supply leaves: %w", err) + } + + // Marshall spent commitment outpoint. + var spentCommitmentOutpoint *taprpc.OutPoint + commitment.SpentCommitment.WhenSome(func(point wire.OutPoint) { + spentCommitmentOutpoint = &taprpc.OutPoint{ + Txid: point.Hash[:], + OutputIndex: point.Index, + } + }) + + req := &unirpc.InsertSupplyCommitRequest{ + GroupKey: &unirpc.InsertSupplyCommitRequest_GroupKeyBytes{ + GroupKeyBytes: groupKey.SerializeCompressed(), + }, + ChainData: rpcChainData, + SpentCommitmentOutpoint: spentCommitmentOutpoint, + IssuanceLeaves: issuanceLeaves, + BurnLeaves: burnLeaves, + IgnoreLeaves: ignoreLeaves, + } + + _, err = r.conn.InsertSupplyCommit(ctx, req) + if err != nil { + return fmt.Errorf("unable to insert supply commitment: %w", err) + } + + srvrLog.Infof("[RpcSupplySync.InsertSupplyCommit]: succeeded in "+ + "inserting supply commitment "+ + "(server_addr=%s, asset=%s, supply_tree_root_hash=%s)", + r.serverAddr.HostStr(), assetSpec.String(), + commitment.SupplyRoot.NodeHash().String()) + + return nil +} + +// Close closes the RPC connection to the universe server. +func (r *RpcSupplySync) Close() error { + if r.conn != nil && r.conn.ClientConn != nil { + return r.conn.ClientConn.Close() + } + return nil +} + +// marshalSupplyCommitChainData converts a supplycommit.RootCommitment and +// supplycommit.ChainProof into a combined RPC SupplyCommitChainData. +func marshalSupplyCommitChainData( + rootCommitment supplycommit.RootCommitment, + chainProof supplycommit.ChainProof) (*unirpc.SupplyCommitChainData, + error) { + + // Serialize the transaction. + var txnBuf bytes.Buffer + err := rootCommitment.Txn.Serialize(&txnBuf) + if err != nil { + return nil, fmt.Errorf("unable to serialize transaction: %w", + err) + } + + // Serialize the block header. + var headerBuf bytes.Buffer + err = chainProof.Header.Serialize(&headerBuf) + if err != nil { + return nil, fmt.Errorf("unable to serialize block header: %w", + err) + } + + // Serialize the merkle proof. + var merkleProofBuf bytes.Buffer + err = chainProof.MerkleProof.Encode(&merkleProofBuf) + if err != nil { + return nil, fmt.Errorf("unable to encode merkle proof: %w", + err) + } + + // nolint: lll + rpcChainData := &unirpc.SupplyCommitChainData{ + Txn: txnBuf.Bytes(), + TxOutIdx: rootCommitment.TxOutIdx, + InternalKey: rootCommitment.InternalKey.PubKey.SerializeCompressed(), + OutputKey: rootCommitment.OutputKey.SerializeCompressed(), + SupplyRootHash: fn.ByteSlice(rootCommitment.SupplyRoot.NodeHash()), + SupplyRootSum: rootCommitment.SupplyRoot.NodeSum(), + BlockHeader: headerBuf.Bytes(), + BlockHeight: chainProof.BlockHeight, + TxBlockMerkleProof: merkleProofBuf.Bytes(), + TxIndex: chainProof.TxIndex, + } + + // Handle optional commitment block hash. + rootCommitment.CommitmentBlock.WhenSome( + func(block supplycommit.CommitmentBlock) { + rpcChainData.BlockHash = block.Hash[:] + }, + ) + + return rpcChainData, nil +} From 0d4a700c0bb21b4ee66b6d2ac7fda1a8eca334f9 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 17:08:29 +0100 Subject: [PATCH 24/51] tapcfg: pass the supply syncer into the supply commit manager --- tapcfg/server.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tapcfg/server.go b/tapcfg/server.go index 4421c0790..36a4636a1 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -519,6 +519,16 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, ) supplyCommitStore := tapdb.NewSupplyCommitMachine(supplyCommitDb) + // Setup supply syncer. + supplySyncerStore := tapdb.NewSupplySyncerStore(uniDB) + supplySyncer := supplyverifier.NewSupplySyncer( + supplyverifier.SupplySyncerConfig{ + ClientFactory: tap.NewRpcSupplySync, + Store: supplySyncerStore, + UniverseFederationView: federationDB, + }, + ) + // Create the supply commitment state machine manager, which is used to // manage the supply commitment state machines for each asset group. supplyCommitManager := supplycommit.NewManager( @@ -529,6 +539,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, AssetLookup: tapdbAddrBook, KeyRing: keyRing, Chain: chainBridge, + SupplySyncer: &supplySyncer, DaemonAdapters: lndFsmDaemonAdapters, StateLog: supplyCommitStore, ChainParams: *tapChainParams.Params, From f92176d119a0866746442fae0aee37a0db196832 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 26 Aug 2025 17:35:57 +0100 Subject: [PATCH 25/51] supplycommit: improve robustness of ApplyTreeUpdates Enhances ApplyTreeUpdates by ensuring that an empty subtree is included for each relevant subtree type when appropriate. This makes the function's return value more consistent and reliable. --- universe/supplycommit/env.go | 7 +++++++ universe/supplycommit/transitions.go | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 96a3eeec5..36b5a0e53 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -62,6 +62,13 @@ func (s SupplySubTree) String() string { } } +// AllSupplySubTrees contains all possible valid SupplySubTree values. +var AllSupplySubTrees = []SupplySubTree{ + MintTreeType, + BurnTreeType, + IgnoreTreeType, +} + // UniverseKey is the key used to identify the universe in the supply tree. This // is scoped to a root supply tree for a given asset specifier. func (s SupplySubTree) UniverseKey() [32]byte { diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index a9eabef27..fe4f3ccd2 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -217,14 +217,24 @@ func ApplyTreeUpdates(supplyTrees SupplyTrees, // Create a copy of the input map to avoid mutating the original. updatedSupplyTrees := make(SupplyTrees) - for k, v := range supplyTrees { - // Create a new tree for each entry in the map. + + // To ensure consistency, we'll create a new empty tree for any subtree + // types that don't exist in the given subtree map. + for _, subtreeType := range AllSupplySubTrees { + subtree, exists := supplyTrees[subtreeType] + if !exists { + updatedSupplyTrees[subtreeType] = + mssmt.NewCompactedTree(mssmt.NewDefaultStore()) + continue + } + + // Copy existing subtree to the new map. newTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore()) - if err := v.Copy(ctx, newTree); err != nil { + if err := subtree.Copy(ctx, newTree); err != nil { return nil, fmt.Errorf("unable to copy tree: %w", err) } - updatedSupplyTrees[k] = newTree + updatedSupplyTrees[subtreeType] = newTree } // TODO(roasbeef): make new copy routine, passes in tree to copy into From 073b5d5eaf4ecddb714e9973eed4917b1fda88c6 Mon Sep 17 00:00:00 2001 From: ffranr Date: Sat, 23 Aug 2025 01:06:01 +0100 Subject: [PATCH 26/51] supplyverifier: add verification logic and use in uni server insert Adds a verifier component to encapsulate all supply commit verification logic. The verifier is used in the Manager.InsertSupplyCommit method, which allows a universe server tapd node to verify supply commitments before inserting them into the local universe server DB. The actual DB insertion logic will be added in a subsequent commit. --- tapcfg/server.go | 1 + universe/supplycommit/env.go | 155 ++++++++++- universe/supplyverifier/env.go | 13 + universe/supplyverifier/manager.go | 24 +- universe/supplyverifier/verifier.go | 395 ++++++++++++++++++++++++++++ 5 files changed, 580 insertions(+), 8 deletions(-) create mode 100644 universe/supplyverifier/verifier.go diff --git a/tapcfg/server.go b/tapcfg/server.go index 36a4636a1..395eb98f3 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -553,6 +553,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, supplyverifier.ManagerCfg{ Chain: chainBridge, SupplyCommitView: supplyCommitStore, + SupplyTreeView: supplyTreeStore, IssuanceSubscriptions: universeSyncer, DaemonAdapters: lndFsmDaemonAdapters, }, diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 36b5a0e53..e14f947c7 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -1,6 +1,7 @@ package supplycommit import ( + "bytes" "context" "crypto/sha256" "fmt" @@ -25,6 +26,12 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) +var ( + // ErrNoBlockInfo is returned when a root commitment is expected to have + // block information, but it is missing. + ErrNoBlockInfo = fmt.Errorf("no block info available") +) + const ( // DefaultCommitConfTarget is the default confirmation target used when // crafting the commitment transaction. This is used in fee estimation. @@ -110,6 +117,53 @@ type SupplyLeaves struct { IgnoreLeafEntries []NewIgnoreEvent } +// AllUpdates returns a slice of all supply update events contained within +// the SupplyLeaves instance. This includes mints, burns, and ignores. +func (s SupplyLeaves) AllUpdates() []SupplyUpdateEvent { + mint := func(e NewMintEvent) SupplyUpdateEvent { + return &e + } + burn := func(e NewBurnEvent) SupplyUpdateEvent { + return &e + } + ignore := func(e NewIgnoreEvent) SupplyUpdateEvent { + return &e + } + allUpdates := make( + []SupplyUpdateEvent, 0, len(s.IssuanceLeafEntries)+ + len(s.BurnLeafEntries)+len(s.IgnoreLeafEntries), + ) + allUpdates = append(allUpdates, fn.Map(s.IssuanceLeafEntries, mint)...) + allUpdates = append(allUpdates, fn.Map(s.BurnLeafEntries, burn)...) + allUpdates = append(allUpdates, fn.Map(s.IgnoreLeafEntries, ignore)...) + + return allUpdates +} + +// Validate performs basic validation on the supply leaves. +func (s SupplyLeaves) Validate() error { + // Block height must be non-zero for all leaves. + for _, leaf := range s.IssuanceLeafEntries { + if leaf.BlockHeight() == 0 { + return fmt.Errorf("mint leaf has zero block height") + } + } + + for _, leaf := range s.BurnLeafEntries { + if leaf.BlockHeight() == 0 { + return fmt.Errorf("burn leaf has zero block height") + } + } + + for _, leaf := range s.IgnoreLeafEntries { + if leaf.BlockHeight() == 0 { + return fmt.Errorf("ignore leaf has zero block height") + } + } + + return nil +} + // NewSupplyLeavesFromEvents creates a SupplyLeaves instance from a slice of // SupplyUpdateEvent instances. func NewSupplyLeavesFromEvents(events []SupplyUpdateEvent) (SupplyLeaves, @@ -252,10 +306,17 @@ type PreCommitment struct { // TxIn returns the transaction input that corresponds to the pre-commitment. func (p *PreCommitment) TxIn() *wire.TxIn { return &wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: p.MintingTxn.TxHash(), - Index: p.OutIdx, - }, + PreviousOutPoint: p.OutPoint(), + } +} + +// OutPoint returns the outpoint that corresponds to the pre-commitment output. +// This is the output that is spent by the supply commitment anchoring +// transaction. +func (p *PreCommitment) OutPoint() wire.OutPoint { + return wire.OutPoint{ + Hash: p.MintingTxn.TxHash(), + Index: p.OutIdx, } } @@ -379,6 +440,92 @@ func (r *RootCommitment) TapscriptRoot() ([]byte, error) { return computeSupplyCommitTapscriptRoot(supplyRootHash) } +// VerifyChainAnchor checks that the on-chain information is correct. +func (r *RootCommitment) VerifyChainAnchor(merkleVerifier proof.MerkleVerifier, + headerVerifier proof.HeaderVerifier) error { + + block, err := r.CommitmentBlock.UnwrapOrErr(ErrNoBlockInfo) + if err != nil { + return fmt.Errorf("unable to verify root commitment: %w", err) + } + + if block.MerkleProof == nil { + return fmt.Errorf("merkle proof is missing") + } + + if block.BlockHeader == nil { + return fmt.Errorf("block header is missing") + } + + if block.Hash != block.BlockHeader.BlockHash() { + return fmt.Errorf("block hash %v does not match block header "+ + "hash %v", block.Hash, block.BlockHeader.BlockHash()) + } + + if r.Txn == nil { + return fmt.Errorf("root commitment transaction is missing") + } + + if r.SupplyRoot == nil { + return fmt.Errorf("supply root is missing") + } + + err = fn.MapOptionZ( + r.SpentCommitment, func(prevOut wire.OutPoint) error { + if !proof.TxSpendsPrevOut(r.Txn, &prevOut) { + return fmt.Errorf("commitment TX doesn't " + + "spend previous commitment outpoint") + } + + return nil + }, + ) + if err != nil { + return fmt.Errorf("unable to verify spent commitment: %w", err) + } + + err = merkleVerifier( + r.Txn, block.MerkleProof, block.BlockHeader.MerkleRoot, + ) + if err != nil { + return fmt.Errorf("unable to verify merkle proof: %w", err) + } + + err = headerVerifier(*block.BlockHeader, block.Height) + if err != nil { + return fmt.Errorf("unable to verify block header: %w", err) + } + + if r.TxOutIdx >= uint32(len(r.Txn.TxOut)) { + return fmt.Errorf("tx out index %d is out of bounds for "+ + "transaction with %d outputs", r.TxOutIdx, + len(r.Txn.TxOut)) + } + + txOut := r.Txn.TxOut[r.TxOutIdx] + expectedOut, _, err := RootCommitTxOut( + r.InternalKey.PubKey, nil, r.SupplyRoot.NodeHash(), + ) + if err != nil { + return fmt.Errorf("unable to create expected output: %w", err) + } + + if txOut.Value != expectedOut.Value { + return fmt.Errorf("tx out value %d does not match expected "+ + "value %d", txOut.Value, expectedOut.Value) + } + + if !bytes.Equal(txOut.PkScript, expectedOut.PkScript) { + return fmt.Errorf("tx out pk script %x does not match "+ + "expected pk script %x", txOut.PkScript, + expectedOut.PkScript) + } + + // Everything that we can check just from the static information + // provided checks out. + return nil +} + // RootCommitTxOut returns the transaction output that corresponds to the root // commitment. This is used to create a new commitment output. func RootCommitTxOut(internalKey *btcec.PublicKey, diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go index 53a32574a..bf6bb5136 100644 --- a/universe/supplyverifier/env.go +++ b/universe/supplyverifier/env.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/mssmt" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe/supplycommit" lfn "github.com/lightningnetwork/lnd/fn/v2" @@ -15,6 +16,11 @@ var ( // ErrCommitmentNotFound is returned when a supply commitment is not // found. ErrCommitmentNotFound = fmt.Errorf("commitment not found") + + // ErrPrevCommitmentNotFound is returned when we try to fetch a + // previous supply commitment, but it is not found in the database. + ErrPrevCommitmentNotFound = fmt.Errorf("previous supply commitment " + + "not found") ) // SupplyCommitView is an interface that is used to look up supply commitments @@ -52,6 +58,13 @@ type SupplyCommitView interface { assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) } +type SupplyTreeView interface { + // FetchSupplyTrees returns a copy of the root supply tree and subtrees + // for the given asset spec. + FetchSupplyTrees(ctx context.Context, spec asset.Specifier) (mssmt.Tree, + *supplycommit.SupplyTrees, error) +} + // Environment is a struct that holds all the dependencies that the supply // verifier needs to carry out its duties. type Environment struct { diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index d9f3f2014..f0141d715 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -65,6 +65,9 @@ type ManagerCfg struct { // pre-commitments. SupplyCommitView SupplyCommitView + // SupplyTreeView is used to fetch supply leaves by height. + SupplyTreeView SupplyTreeView + // SupplySyncer is used to retrieve supply leaves from a universe and // persist them to the local database. SupplySyncer SupplySyncer @@ -211,10 +214,23 @@ func (m *Manager) InsertSupplyCommit(ctx context.Context, assetSpec asset.Specifier, commitment supplycommit.RootCommitment, leaves supplycommit.SupplyLeaves) error { - // TODO(ffranr): Verify supply commit without starting a state machine. - // This is effectively where universe server supply commit verification - // takes place. Once verified, we can store the commitment in the - // local database. + // First, we verify the supply commitment to ensure it is valid and + // consistent with the given supply leaves. + verifier, err := NewVerifier( + m.cfg.Chain, m.cfg.SupplyCommitView, m.cfg.SupplyTreeView, + ) + if err != nil { + return fmt.Errorf("unable to create supply verifier: %w", err) + } + + err = verifier.VerifyCommit(ctx, assetSpec, commitment, leaves) + if err != nil { + return fmt.Errorf("supply commitment verification failed: %w", + err) + } + + // TODO(ffranr): Insert the commitment and leaves into the local + // database. return nil } diff --git a/universe/supplyverifier/verifier.go b/universe/supplyverifier/verifier.go new file mode 100644 index 000000000..c3b765458 --- /dev/null +++ b/universe/supplyverifier/verifier.go @@ -0,0 +1,395 @@ +package supplyverifier + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/mssmt" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tapgarden" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" +) + +// VerifierCfg is the configuration for the verifier. +type VerifierCfg struct { + // Chain is our access to the current main chain. + Chain tapgarden.ChainBridge + + // SupplyCommitView allows us to look up supply commitments and + // pre-commitments. + SupplyCommitView SupplyCommitView + + // SupplyTreeView is used to fetch supply leaves by height. + SupplyTreeView SupplyTreeView +} + +// Verifier is responsible for verifying supply commitments. +type Verifier struct { + // cfg is the configuration for the verifier. + cfg VerifierCfg +} + +// NewVerifier creates a new Verifier with the given configuration. +func NewVerifier(chain tapgarden.ChainBridge, + supplyCommitView SupplyCommitView, + supplyTreeView SupplyTreeView) (Verifier, error) { + + var zero Verifier + + if chain == nil { + return zero, fmt.Errorf("chain is required") + } + + if supplyCommitView == nil { + return zero, fmt.Errorf("supply commit view is required") + } + + if supplyTreeView == nil { + return zero, fmt.Errorf("supply tree view is required") + } + + return Verifier{ + cfg: VerifierCfg{ + Chain: chain, + SupplyCommitView: supplyCommitView, + SupplyTreeView: supplyTreeView, + }, + }, nil +} + +// ensurePrecommitsSpent verifies that all unspent pre-commitment outputs for +// the specified asset group, which could have been spent by the supply +// commitment transaction, were actually spent. +func (v *Verifier) ensurePrecommitsSpent(ctx context.Context, + assetSpec asset.Specifier, + commitment supplycommit.RootCommitment) error { + + // Fetch all unspent pre-commitment outputs for the asset group. + allPreCommits, err := v.cfg.SupplyCommitView.UnspentPrecommits( + ctx, assetSpec, + ).Unpack() + if err != nil { + return fmt.Errorf("unable to fetch unspent pre-commitments: %w", + err) + } + + // Filter pre-commits to only include those that are at block heights + // less than or equal to the commitment's anchor block height. All + // unspent pre-commitments at or before the commitment's anchor block + // height must be spent by the commitment transaction. + commitmentBlock, err := commitment.CommitmentBlock.UnwrapOrErr( + fmt.Errorf("missing commitment block"), + ) + if err != nil { + return err + } + + var preCommits []supplycommit.PreCommitment + for idx := range allPreCommits { + preCommit := allPreCommits[idx] + if preCommit.BlockHeight <= commitmentBlock.Height { + preCommits = append(preCommits, preCommit) + } + } + + // Keep track of all matched pre-commitment outpoints to ensure that + // we spend each one exactly once. + matchedOutPoints := make(map[string]struct{}) + for idxCommitTxIn := range commitment.Txn.TxIn { + commitTxIn := commitment.Txn.TxIn[idxCommitTxIn] + + for idxPreCommit := range preCommits { + preCommit := preCommits[idxPreCommit] + preCommitOutPoint := preCommit.OutPoint() + + if commitTxIn.PreviousOutPoint == preCommitOutPoint { + opStr := preCommitOutPoint.String() + matchedOutPoints[opStr] = struct{}{} + break + } + } + } + + if len(matchedOutPoints) != len(preCommits) { + // Log which pre-commitment outpoints were not matched. + var unmatched []string + for idx := range preCommits { + preCommit := preCommits[idx] + preCommitOutPoint := preCommit.OutPoint() + opStr := preCommitOutPoint.String() + if _, ok := matchedOutPoints[opStr]; !ok { + unmatched = append(unmatched, opStr) + } + } + + log.Errorf("Unmatched pre-commitment outpoints in supply "+ + "commit anchor tx inputs set:\n%s", + strings.Join(unmatched, "\n")) + + return fmt.Errorf("supply commitment does not spend all "+ + "known pre-commitments: expected %d, found %d", + len(preCommits), len(matchedOutPoints)) + } + + return nil +} + +// verifyInitialCommit verifies the first (starting) supply commitment for a +// given asset group. +func (v *Verifier) verifyInitialCommit(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error { + + // Check to ensure that we don't already have a starting + // commitment for the asset group. If we do, then we require a spent + // outpoint to be set on the commitment or that the outpoint is + // the same as the given commitment outpoint. + initCommit, err := v.cfg.SupplyCommitView.FetchStartingCommitment( + ctx, assetSpec, + ) + switch { + case err == nil: + // An initial commitment was found for the asset group. This + // means the given supply commitment is either the initial + // commitment itself, or it is missing a spent outpoint. + if initCommit.CommitPoint() == commitment.CommitPoint() { + // The spent outpoint matches the current commitment + // outpoint. This indicates the commitment has already + // been verified and stored, so we return nil to + // signal verification is complete. + return nil + } + + return fmt.Errorf("found initial commitment for asset group; "+ + "cannot insert supply commitment without a specified "+ + "spent supply commit outpoint (asset=%s)", + assetSpec.String()) + + case errors.Is(err, ErrCommitmentNotFound): + // This is the first commitment for the asset group, so we can + // proceed without a spent outpoint. + + default: + return fmt.Errorf("failed to check for starting commitment: "+ + "%w", err) + } + + // Confirm that the given supply commitment transaction spends all known + // unspent pre-commitment outputs. Pre-commitment outputs are outputs + // that were created at the time of asset issuance, and are the + // starting point for the supply commitment chain. Each asset issuance + // anchor transaction can have at most one pre-commitment output. + err = v.ensurePrecommitsSpent(ctx, assetSpec, commitment) + if err != nil { + return fmt.Errorf("unable to verify pre-commitment spends: %w", + err) + } + + // Confirm that the given supply leaves are consistent with the + // given commitment root. + // + // Apply leaves to empty supply trees to generate the initial set of + // supply subtrees. + supplyTrees, err := supplycommit.ApplyTreeUpdates( + supplycommit.SupplyTrees{}, leaves.AllUpdates(), + ) + if err != nil { + return fmt.Errorf("unable to generate supply subtrees from "+ + "supply leaves: %w", err) + } + + // Create a new empty root supply tree and apply the supply subtrees + // generated above. + emptyRootSupplyTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore()) + + rootSupplyTree, err := supplycommit.UpdateRootSupplyTree( + ctx, emptyRootSupplyTree, supplyTrees, + ) + if err != nil { + return fmt.Errorf("unable to formulate root supply tree: %w", + err) + } + + // Ensure that the root of the formulated supply tree matches the + // commitment root. + genRoot, err := rootSupplyTree.Root(ctx) + if err != nil { + return fmt.Errorf("unable to compute root of generated "+ + "supply tree: %w", err) + } + + if genRoot.NodeHash() != commitment.SupplyRoot.NodeHash() { + return fmt.Errorf("generated supply tree root does not match " + + "commitment supply root") + } + + return nil +} + +// verifyIncrementalCommit verifies an incremental supply commitment for a +// given asset group. Verification succeeds only if the previous supply +// commitment is known and verified, and the given supply leaves are +// consistent with the commitment root. +func (v *Verifier) verifyIncrementalCommit(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error { + + // Fetch previous supply commitment based on the spent outpoint. This + // step ensures that we have already verified the previous + // commitment, and that it is present in the database. + spentOutPoint, err := commitment.SpentCommitment.UnwrapOrErr( + fmt.Errorf("missing spent supply commitment outpoint"), + ) + if err != nil { + return err + } + + spentCommit, err := + v.cfg.SupplyCommitView.FetchCommitmentByOutpoint( + ctx, assetSpec, spentOutPoint, + ) + if err != nil { + return ErrPrevCommitmentNotFound + } + + // Check that the given commitment spends the previous commitment's + // outpoint that is referenced by the given spent outpoint field. + checkSpendPrevOutPoint := false + for idx := range commitment.Txn.TxIn { + txIn := commitment.Txn.TxIn[idx] + if txIn.PreviousOutPoint == spentOutPoint { + checkSpendPrevOutPoint = true + break + } + } + + if !checkSpendPrevOutPoint { + return fmt.Errorf("supply commitment does not spend " + + "provided previous commitment outpoint") + } + + // Verify that every unspent pre-commitment output eligible by block + // height is actually spent by the supply commitment transaction. + err = v.ensurePrecommitsSpent(ctx, assetSpec, commitment) + if err != nil { + return fmt.Errorf("unable to verify pre-commitment spends: %w", + err) + } + + // Get latest supply root tree and subtrees from the local db. Ensure + // that they correspond to the spent supply commitment outpoint. + spentRootTree, spentSubtrees, err := + v.cfg.SupplyTreeView.FetchSupplyTrees( + ctx, assetSpec, + ) + if err != nil { + return fmt.Errorf("unable to fetch spent root supply tree: %w", + err) + } + + storedSpentRoot, err := spentRootTree.Root(ctx) + if err != nil { + return fmt.Errorf("unable to compute root of local spent "+ + "supply tree: %w", err) + } + + if storedSpentRoot.NodeHash() != spentCommit.SupplyRoot.NodeHash() { + return fmt.Errorf("local spent supply tree root does not " + + "match spent commitment supply root") + } + + // Apply new leaves to the spent subtrees to generate the new set of + // supply subtrees. + newSupplyTrees, err := supplycommit.ApplyTreeUpdates( + *spentSubtrees, leaves.AllUpdates(), + ) + if err != nil { + return fmt.Errorf("unable to apply tree updates to spent "+ + "commitment: %w", err) + } + + // Reconstruct the root supply tree by applying the new leaves to + // the previous root supply tree. + expectedSupplyTree, err := supplycommit.UpdateRootSupplyTree( + ctx, spentRootTree, newSupplyTrees, + ) + if err != nil { + return fmt.Errorf("unable to generate expected root supply "+ + "tree: %w", err) + } + + expectedRoot, err := expectedSupplyTree.Root(ctx) + if err != nil { + return fmt.Errorf("unable to compute root of expected supply "+ + "tree: %w", err) + } + + // Ensure that the root of the reconstructed supply tree matches + // the commitment root. + if expectedRoot.NodeHash() != commitment.SupplyRoot.NodeHash() { + return fmt.Errorf("expected supply tree root does not match " + + "commitment supply root") + } + + return nil +} + +// VerifyCommit verifies a supply commitment for a given asset group. +// Verification succeeds only if all previous supply commitment dependencies +// are known and verified. The dependency chain must be traceable back to the +// asset issuance anchoring transaction and its pre-commitment output(s). +func (v *Verifier) VerifyCommit(ctx context.Context, + assetSpec asset.Specifier, commitment supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error { + + // TODO(ffranr): Consider: should we require some leaves to be present? + // Or for forward compatibility, allow no leaves? + + // Perform static on-chain verification of the supply commitment's + // anchoring block header. This provides a basic proof-of-work guarantee + // that gates further verification steps. + headerVerifier := tapgarden.GenHeaderVerifier(ctx, v.cfg.Chain) + err := commitment.VerifyChainAnchor( + proof.DefaultMerkleVerifier, headerVerifier, + ) + if err != nil { + return fmt.Errorf("unable to verify supply commitment: %w", err) + } + + // Perform basic validation of the provided supply leaves. + err = leaves.Validate() + if err != nil { + return fmt.Errorf("supply leaves validation failed: %w", err) + } + + // Attempt to fetch the supply commitment by its outpoint, to + // ensure that it is not already present in the database. + _, err = v.cfg.SupplyCommitView.FetchCommitmentByOutpoint( + ctx, assetSpec, commitment.CommitPoint(), + ) + switch { + case err == nil: + // Found commitment, assume already verified and stored. + return nil + + case errors.Is(err, ErrCommitmentNotFound): + // Do nothing, continue to verification of given commitment. + + default: + return fmt.Errorf("failed to check for existing supply "+ + "commitment with given outpoint: %w", err) + } + + // If the commitment does not specify a spent outpoint, then we dispatch + // to the initial commitment verification routine. + if commitment.SpentCommitment.IsNone() { + return v.verifyInitialCommit(ctx, assetSpec, commitment, leaves) + } + + // Otherwise, we dispatch to the incremental commitment verification + // routine. + return v.verifyIncrementalCommit(ctx, assetSpec, commitment, leaves) +} From a282e5e2556d64067c62d7c0e48a7f6bbbb53cd7 Mon Sep 17 00:00:00 2001 From: ffranr Date: Sat, 23 Aug 2025 01:06:16 +0100 Subject: [PATCH 27/51] supplyverifier+tapdb: add supply commit insert functionality Adds the InsertSupplyCommit method to tapdb for inserting supply commitments into the local DB. The supply verifier now uses this method to complete the supply commit insertion flow. This change resolves the final TODO, enabling a universe server tapd node to verify and store a given supply commitment. --- tapdb/supply_commit.go | 194 +++++++++++++++++++++++++++++ universe/supplyverifier/env.go | 5 + universe/supplyverifier/manager.go | 7 +- 3 files changed, 202 insertions(+), 4 deletions(-) diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 49a9f15ab..033bb8788 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -907,6 +907,200 @@ func (s *SupplyCommitMachine) InsertSignedCommitTx(ctx context.Context, }) } +// InsertSupplyCommit inserts a new, fully complete supply commitment into the +// database. +func (s *SupplyCommitMachine) InsertSupplyCommit(ctx context.Context, + assetSpec asset.Specifier, commit supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error { + + groupKey := assetSpec.UnwrapGroupKeyToPtr() + if groupKey == nil { + return ErrMissingGroupKey + } + groupKeyBytes := groupKey.SerializeCompressed() + + commitTx := commit.Txn + internalKey := commit.InternalKey + outputKey := commit.OutputKey + outputIndex := commit.TxOutIdx + + block, err := commit.CommitmentBlock.UnwrapOrErr( + supplycommit.ErrNoBlockInfo, + ) + if err != nil { + return fmt.Errorf("failed to unwrap commitment block: %w", err) + } + + writeTx := WriteTxOption() + return s.db.ExecTx(ctx, writeTx, func(db SupplyCommitStore) error { + // Next, we'll upsert the chain transaction on disk. The block + // related fields are nil as this hasn't been confirmed yet. + var txBytes bytes.Buffer + if err := commitTx.Serialize(&txBytes); err != nil { + return fmt.Errorf("failed to serialize commit "+ + "tx: %w", err) + } + txid := commitTx.TxHash() + chainTxID, err := db.UpsertChainTx(ctx, UpsertChainTxParams{ + Txid: txid[:], + RawTx: txBytes.Bytes(), + }) + if err != nil { + return fmt.Errorf("failed to upsert commit chain tx: "+ + "%w", err) + } + + // Upsert the internal key to get its ID. We assume key family + // and index 0 for now, as this key is likely externally. + internalKeyID, err := db.UpsertInternalKey(ctx, InternalKey{ + RawKey: internalKey.PubKey.SerializeCompressed(), + KeyFamily: int32(internalKey.Family), + KeyIndex: int32(internalKey.Index), + }) + if err != nil { + return fmt.Errorf("failed to upsert internal key %x: "+ + "%w", internalKey.PubKey.SerializeCompressed(), + err) + } + + // Now we fetch the previous commitment that is being spent by + // this one. + var spentCommitment sql.NullInt64 + err = fn.MapOptionZ( + commit.SpentCommitment, func(op wire.OutPoint) error { + q := sqlc.QuerySupplyCommitmentByOutpointParams{ + GroupKey: groupKeyBytes, + Txid: op.Hash[:], + OutputIndex: sqlInt32(op.Index), + } + row, err := db.QuerySupplyCommitmentByOutpoint( + ctx, q, + ) + if err != nil { + return fmt.Errorf("failed to query "+ + "spent commitment: %w", err) + } + + spentCommitment = sqlInt64( + row.SupplyCommitment.CommitID, + ) + + return nil + }, + ) + if err != nil { + return fmt.Errorf("failed to fetch spent commitment: "+ + "%w", err) + } + + // Insert the new commitment record. Chain details (block + // height, header, proof, output index) are NULL at this stage. + params := sqlc.InsertSupplyCommitmentParams{ + GroupKey: groupKeyBytes, + ChainTxnID: chainTxID, + InternalKeyID: internalKeyID, + OutputKey: outputKey.SerializeCompressed(), + SupplyRootHash: nil, + SupplyRootSum: sql.NullInt64{}, + OutputIndex: sqlInt32(outputIndex), + SpentCommitment: spentCommitment, + } + newCommitmentID, err := db.InsertSupplyCommitment(ctx, params) + if err != nil { + return fmt.Errorf("failed to insert new supply "+ + "commitment: %w", err) + } + + // Update the commitment record with the calculated root hash + // and sum. + finalRootSupplyRoot, err := applySupplyUpdatesInternal( + ctx, db, assetSpec, leaves.AllUpdates(), + ) + if err != nil { + return fmt.Errorf("failed to apply SMT updates: "+ + "%w", err) + } + finalRootHash := finalRootSupplyRoot.NodeHash() + finalRootSum := finalRootSupplyRoot.NodeSum() + err = db.UpdateSupplyCommitmentRoot( + ctx, UpdateSupplyCommitmentRootParams{ + CommitID: newCommitmentID, + SupplyRootHash: finalRootHash[:], + SupplyRootSum: sqlInt64(int64(finalRootSum)), + }, + ) + if err != nil { + return fmt.Errorf("failed to update commitment root "+ + "hash/sum for commit %d: %w", + newCommitmentID, err) + } + + // Next, we'll serialize the merkle proofs and block header, so + // we can update them on disk. + var ( + proofBuf bytes.Buffer + headerBuf bytes.Buffer + ) + + err = block.MerkleProof.Encode(&proofBuf) + if err != nil { + return fmt.Errorf("failed to encode "+ + "merkle proof: %w", err) + } + err = block.BlockHeader.Serialize(&headerBuf) + if err != nil { + return fmt.Errorf("failed to "+ + "serialize block header: %w", + err) + } + blockHeight := sqlInt32(block.Height) + + // With all the information serialized above, we'll now update + // the chain proof information for this current supply commit. + err = db.UpdateSupplyCommitmentChainDetails( + ctx, SupplyCommitChainDetails{ + CommitID: newCommitmentID, + MerkleProof: proofBuf.Bytes(), + OutputIndex: sqlInt32(commit.TxOutIdx), + BlockHeader: headerBuf.Bytes(), + ChainTxnID: chainTxID, + BlockHeight: blockHeight, + }, + ) + if err != nil { + return fmt.Errorf("failed to update commitment chain "+ + "details: %w", err) + } + + // Also update the chain_txns record itself with the + // confirmation details (block hash, height, index). + var commitTxBytes bytes.Buffer + err = commit.Txn.Serialize(&commitTxBytes) + if err != nil { + return fmt.Errorf("failed to serialize commit tx for "+ + "update: %w", err) + } + commitTxid := commit.Txn.TxHash() + + _, err = db.UpsertChainTx(ctx, UpsertChainTxParams{ + Txid: commitTxid[:], + RawTx: commitTxBytes.Bytes(), + ChainFees: 0, + BlockHash: lnutils.ByteSlice( + block.BlockHeader.BlockHash(), + ), + BlockHeight: blockHeight, + TxIndex: sqlInt32(block.TxIndex), + }) + if err != nil { + return fmt.Errorf("failed to update chain_txns "+ + "confirmation: %w", err) + } + + return nil + }) +} + // CommitState commits the state of the state machine to disk. func (s *SupplyCommitMachine) CommitState(ctx context.Context, assetSpec asset.Specifier, state supplycommit.State) error { diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go index bf6bb5136..58616cdec 100644 --- a/universe/supplyverifier/env.go +++ b/universe/supplyverifier/env.go @@ -56,6 +56,11 @@ type SupplyCommitView interface { // ErrCommitmentNotFound. FetchStartingCommitment(ctx context.Context, assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) + + // InsertSupplyCommit inserts a supply commitment into the database. + InsertSupplyCommit(ctx context.Context, + assetSpec asset.Specifier, commit supplycommit.RootCommitment, + leaves supplycommit.SupplyLeaves) error } type SupplyTreeView interface { diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index f0141d715..2d03eec6c 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -229,10 +229,9 @@ func (m *Manager) InsertSupplyCommit(ctx context.Context, err) } - // TODO(ffranr): Insert the commitment and leaves into the local - // database. - - return nil + return m.cfg.SupplyCommitView.InsertSupplyCommit( + ctx, assetSpec, commitment, leaves, + ) } // CanHandle determines if the state machine associated with the given asset From d47c17bb0a4e03f32c9f518c5699377fb276b9de Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 25 Aug 2025 18:43:17 +0100 Subject: [PATCH 28/51] mssmt: add CopyFilter to Tree interface and tree implementations Introduce a CopyFilter method to the Tree interface, along with implementations for both Compact and Full trees. This method enables selective copying of tree nodes using a caller-provided predicate function, similar to the existing Copy method. Include unit tests to verify the new functionality. This will be used to filter leaves from the supply subtree based on a maximum block height, supporting reconstruction of subtrees anchored to the chain via a supply commitment transaction. --- mssmt/compacted_tree.go | 62 ++++++++ mssmt/interface.go | 12 ++ mssmt/tree.go | 61 ++++++++ mssmt/tree_test.go | 308 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 443 insertions(+) diff --git a/mssmt/compacted_tree.go b/mssmt/compacted_tree.go index 84246e502..f8b6383e0 100644 --- a/mssmt/compacted_tree.go +++ b/mssmt/compacted_tree.go @@ -521,6 +521,68 @@ func (t *CompactedTree) Copy(ctx context.Context, targetTree Tree) error { return nil } +// CopyFilter copies all the key-value pairs from the source tree into the +// target tree that pass the filter callback. The filter callback is invoked for +// each leaf-key pair. +func (t *CompactedTree) CopyFilter(ctx context.Context, targetTree Tree, + filterFunc CopyFilterPredicate) error { + + var leaves map[[hashSize]byte]*LeafNode + err := t.store.View(ctx, func(tx TreeStoreViewTx) error { + root, err := tx.RootNode() + if err != nil { + return fmt.Errorf("error getting root node: %w", err) + } + + // Optimization: If the source tree is empty, there's nothing to + // copy. + if IsEqualNode(root, EmptyTree[0]) { + leaves = make(map[[hashSize]byte]*LeafNode) + return nil + } + + // Start recursive collection from the root at depth 0. + leaves, err = collectLeavesRecursive(ctx, tx, root, 0) + if err != nil { + return fmt.Errorf("error collecting leaves: %w", err) + } + + return nil + }) + if err != nil { + return err + } + + // Pass the leaves through the filter callback. + if filterFunc != nil { + var filteredLeaves = make(map[[hashSize]byte]*LeafNode) + + for leafKey, leafNode := range leaves { + include, err := filterFunc(leafKey, *leafNode) + if err != nil { + return fmt.Errorf("filter function for key "+ + "%x: %w", leafKey, err) + } + + if include { + filteredLeaves[leafKey] = leafNode + } + } + + leaves = filteredLeaves + } + + // Insert all found leaves into the target tree using InsertMany for + // efficiency. + _, err = targetTree.InsertMany(ctx, leaves) + if err != nil { + return fmt.Errorf("error inserting leaves into "+ + "target tree: %w", err) + } + + return nil +} + // InsertMany inserts multiple leaf nodes provided in the leaves map within a // single database transaction. func (t *CompactedTree) InsertMany(ctx context.Context, diff --git a/mssmt/interface.go b/mssmt/interface.go index bf3759f0c..4756dee45 100644 --- a/mssmt/interface.go +++ b/mssmt/interface.go @@ -2,6 +2,12 @@ package mssmt import "context" +// CopyFilterPredicate is a type alias for a filter function used in CopyFilter. +// It takes a key and leaf node as input and returns a boolean indicating +// whether to include the leaf in the copy operation. A true value means the +// leaf should be included, while false means it should be excluded. +type CopyFilterPredicate = func([hashSize]byte, LeafNode) (bool, error) + // Tree is an interface defining an abstract MSSMT tree type. type Tree interface { // Root returns the root node of the MS-SMT. @@ -39,4 +45,10 @@ type Tree interface { // Copy copies all the key-value pairs from the source tree into the // target tree. Copy(ctx context.Context, targetTree Tree) error + + // CopyFilter copies all the key-value pairs from the source tree into + // the target tree that pass the filter callback. The filter callback is + // invoked for each leaf-key pair. + CopyFilter(ctx context.Context, targetTree Tree, + filterFunc CopyFilterPredicate) error } diff --git a/mssmt/tree.go b/mssmt/tree.go index 60e812184..8d244c1d6 100644 --- a/mssmt/tree.go +++ b/mssmt/tree.go @@ -440,6 +440,67 @@ func (t *FullTree) Copy(ctx context.Context, targetTree Tree) error { return nil } +// CopyFilter copies all the key-value pairs from the source tree into the +// target tree that pass the filter callback. The filter callback is invoked for +// each leaf-key pair. +func (t *FullTree) CopyFilter(ctx context.Context, targetTree Tree, + filterFunc CopyFilterPredicate) error { + + var leaves map[[hashSize]byte]*LeafNode + err := t.store.View(ctx, func(tx TreeStoreViewTx) error { + root, err := tx.RootNode() + if err != nil { + return fmt.Errorf("error getting root node: %w", err) + } + + // Optimization: If the source tree is empty, there's nothing + // to copy. + if IsEqualNode(root, EmptyTree[0]) { + leaves = make(map[[hashSize]byte]*LeafNode) + return nil + } + + leaves, err = findLeaves(ctx, tx, root, [hashSize]byte{}, 0) + if err != nil { + return fmt.Errorf("error finding leaves: %w", err) + } + + return nil + }) + if err != nil { + return err + } + + // Pass the leaves through the filter callback. + if filterFunc != nil { + var filteredLeaves = make(map[[hashSize]byte]*LeafNode) + + for leafKey, leafNode := range leaves { + include, err := filterFunc(leafKey, *leafNode) + if err != nil { + return fmt.Errorf("filter function for key "+ + "%x: %w", leafKey, err) + } + + if include { + filteredLeaves[leafKey] = leafNode + } + } + + leaves = filteredLeaves + } + + // Insert all found leaves into the target tree using InsertMany for + // efficiency. + _, err = targetTree.InsertMany(ctx, leaves) + if err != nil { + return fmt.Errorf("error inserting leaves into target "+ + "tree: %w", err) + } + + return nil +} + // InsertMany inserts multiple leaf nodes provided in the leaves map within a // single database transaction. func (t *FullTree) InsertMany(ctx context.Context, diff --git a/mssmt/tree_test.go b/mssmt/tree_test.go index 13c7f8791..12b14504d 100644 --- a/mssmt/tree_test.go +++ b/mssmt/tree_test.go @@ -978,6 +978,314 @@ func TestTreeCopy(t *testing.T) { } } +// testFilterScenario tests a single filter scenario for the CopyFilter method. +func testFilterScenario( + t *testing.T, ctx context.Context, sourceTree mssmt.Tree, + targetTree mssmt.Tree, filterFunc mssmt.CopyFilterPredicate, + expectCount int, description string, leaves []treeLeaf, + initialTargetLeavesMap map[[hashSize]byte]*mssmt.LeafNode) { + + // Pre-populate the target tree. + _, err := targetTree.InsertMany(ctx, initialTargetLeavesMap) + require.NoError(t, err) + + // Perform the filtered copy. + err = sourceTree.CopyFilter(ctx, targetTree, filterFunc) + require.NoError(t, err) + + // Calculate expected leaves based on the filter. + expectedLeaves := make(map[[hashSize]byte]*mssmt.LeafNode) + + // Start with initial target leaves. + for key, leaf := range initialTargetLeavesMap { + expectedLeaves[key] = leaf + } + + // Apply filter to source leaves. + if filterFunc == nil { + // Nil filter means include all. + for _, item := range leaves { + expectedLeaves[item.key] = item.leaf + } + } else { + for _, item := range leaves { + include, err := filterFunc(item.key, *item.leaf) + require.NoError(t, err) + if include { + expectedLeaves[item.key] = item.leaf + } + } + } + + // Verify the expected count. + actualFilteredCount := len(expectedLeaves) - len(initialTargetLeavesMap) + require.Equal(t, expectCount, actualFilteredCount, + "filtered leaf count mismatch for %s", description, + ) + + // Create expected state tree for root comparison. + expectedStateStore := mssmt.NewDefaultStore() + expectedStateTree := mssmt.NewFullTree(expectedStateStore) + + _, err = expectedStateTree.InsertMany(ctx, expectedLeaves) + require.NoError(t, err) + + expectedRoot, err := expectedStateTree.Root(ctx) + require.NoError(t, err) + + // Verify the target tree root matches the expected root. + targetRoot, err := targetTree.Root(ctx) + require.NoError(t, err) + require.True( + t, mssmt.IsEqualNode(expectedRoot, targetRoot), + "root mismatch after filtered copy with %s", description, + ) + + // Verify individual leaves in the target tree. + for key, expectedLeaf := range expectedLeaves { + targetLeaf, err := targetTree.Get(ctx, key) + require.NoError(t, err) + require.Equal(t, expectedLeaf, targetLeaf, + "leaf mismatch for key %x with %s", key, description, + ) + } + + // Verify that filtered-out leaves are not present. + if filterFunc == nil { + return + } + + for _, item := range leaves { + include, err := filterFunc(item.key, *item.leaf) + require.NoError(t, err) + if include { + continue + } + + // This leaf should not be in target tree unless it was in + // initial target leaves. + _, wasInitial := initialTargetLeavesMap[item.key] + if wasInitial { + continue + } + + targetLeaf, err := targetTree.Get(ctx, item.key) + require.NoError(t, err) + require.True( + t, targetLeaf.IsEmpty(), + "filtered-out leaf %x should not be present", item.key, + ) + } +} + +// TestTreeCopyFilter tests the CopyFilter method with various filter scenarios. +func TestTreeCopyFilter(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Prepare source trees (Full and Compacted). + sourceFullStore := mssmt.NewDefaultStore() + sourceFullTree := mssmt.NewFullTree(sourceFullStore) + + sourceCompactedStore := mssmt.NewDefaultStore() + sourceCompactedTree := mssmt.NewCompactedTree(sourceCompactedStore) + + leaves := randTree(20) + for _, item := range leaves { + _, err := sourceFullTree.Insert(ctx, item.key, item.leaf) + require.NoError(t, err) + + _, err = sourceCompactedTree.Insert(ctx, item.key, item.leaf) + require.NoError(t, err) + } + + sourceFullRoot, err := sourceFullTree.Root(ctx) + require.NoError(t, err) + + sourceCompactedRoot, err := sourceCompactedTree.Root(ctx) + require.NoError(t, err) + + require.True(t, mssmt.IsEqualNode(sourceFullRoot, sourceCompactedRoot)) + + // Define some leaves to pre-populate the target tree. + initialTargetLeaves := []treeLeaf{ + {key: test.RandHash(), leaf: randLeaf()}, + {key: test.RandHash(), leaf: randLeaf()}, + } + initialTargetLeavesMap := make(map[[hashSize]byte]*mssmt.LeafNode) + for _, item := range initialTargetLeaves { + initialTargetLeavesMap[item.key] = item.leaf + } + + // Get first 5 keys for deterministic filtering. + var excludeKeys [][hashSize]byte + for i, item := range leaves { + if i < 5 { + excludeKeys = append(excludeKeys, item.key) + continue + } + + break + } + + // Define filter scenarios. + // + // nolint: lll + filterScenarios := []struct { + name string + filterFunc mssmt.CopyFilterPredicate + expectCount int + description string + }{ + { + name: "include_all", + filterFunc: func(key [hashSize]byte, leaf mssmt.LeafNode) (bool, error) { + return true, nil + }, + expectCount: len(leaves), + description: "filter that includes all leaves.", + }, + { + name: "exclude_all", + filterFunc: func(key [hashSize]byte, leaf mssmt.LeafNode) (bool, error) { + return false, nil + }, + expectCount: 0, + description: "filter that excludes all leaves.", + }, + { + name: "exclude_five", + filterFunc: func(key [hashSize]byte, leaf mssmt.LeafNode) (bool, error) { + // Exclude exactly 5 specific leaves. + for _, excludeKey := range excludeKeys { + if key == excludeKey { + return false, nil + } + } + return true, nil + }, + expectCount: len(leaves) - 5, + description: "filter that excludes exactly 5 " + + "specific leaves.", + }, + { + name: "nil_filter", + filterFunc: nil, + expectCount: len(leaves), + description: "nil filter should include all leaves.", + }, + } + + // Define test cases for different tree type combinations. + testCases := []struct { + name string + sourceTree mssmt.Tree + makeTarget func() mssmt.Tree + }{ + { + name: "Full -> Full", + sourceTree: sourceFullTree, + makeTarget: func() mssmt.Tree { + return mssmt.NewFullTree( + mssmt.NewDefaultStore(), + ) + }, + }, + { + name: "Full -> Compacted", + sourceTree: sourceFullTree, + makeTarget: func() mssmt.Tree { + return mssmt.NewCompactedTree( + mssmt.NewDefaultStore(), + ) + }, + }, + { + name: "Compacted -> Full", + sourceTree: sourceCompactedTree, + makeTarget: func() mssmt.Tree { + return mssmt.NewFullTree( + mssmt.NewDefaultStore(), + ) + }, + }, + { + name: "Compacted -> Compacted", + sourceTree: sourceCompactedTree, + makeTarget: func() mssmt.Tree { + return mssmt.NewCompactedTree( + mssmt.NewDefaultStore(), + ) + }, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + for _, fs := range filterScenarios { + fs := fs + + t.Run(fs.name, func(t *testing.T) { + t.Parallel() + + testFilterScenario( + t, ctx, tc.sourceTree, + tc.makeTarget(), + fs.filterFunc, fs.expectCount, + fs.description, leaves, + initialTargetLeavesMap, + ) + }) + } + }) + } +} + +// TestTreeCopyFilterError tests error handling in CopyFilter method. +func TestTreeCopyFilterError(t *testing.T) { + t.Parallel() + + leaves := randTree(10) + ctx := context.Background() + + // Prepare source tree. + sourceStore := mssmt.NewDefaultStore() + sourceTree := mssmt.NewFullTree(sourceStore) + + for _, item := range leaves { + _, err := sourceTree.Insert(ctx, item.key, item.leaf) + require.NoError(t, err) + } + + // Prepare target tree. + targetStore := mssmt.NewDefaultStore() + targetTree := mssmt.NewFullTree(targetStore) + + // Test filter function that returns an error. + errorFilter := func(key [hashSize]byte, leaf mssmt.LeafNode) (bool, + error) { + + // Return error for the first key we encounter. + return false, fmt.Errorf("test filter error for key %x", key) + } + + err := sourceTree.CopyFilter(ctx, targetTree, errorFilter) + require.Error(t, err) + require.Contains(t, err.Error(), "filter function for key") + require.Contains(t, err.Error(), "test filter error") + + // Verify target tree remains unchanged after error. + targetRoot, err := targetTree.Root(ctx) + require.NoError(t, err) + require.True(t, mssmt.IsEqualNode(targetRoot, mssmt.EmptyTree[0]), + "target tree should remain empty after filter error") +} + // TestInsertMany tests inserting multiple leaves using the InsertMany method. func TestInsertMany(t *testing.T) { t.Parallel() From 29ad936e649ebe0822da7a18335a4c59588b42b2 Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 25 Aug 2025 19:19:29 +0100 Subject: [PATCH 29/51] supplycommit+tapdb: extend FetchSubTrees with block height end param Add a block height range end parameter to FetchSubTrees, enabling filtering of supply subtree leaves by block height. This allows reconstruction of supply subtrees as they existed at a specific supply commitment block height. This functionality is useful for reproducing a supply commitment at a given block height for syncing purposes. --- rpcserver.go | 2 +- tapdb/supply_tree.go | 113 ++++++++++++++++++++++++++- universe/supplycommit/env.go | 5 +- universe/supplycommit/manager.go | 11 ++- universe/supplycommit/mock.go | 4 +- universe/supplycommit/transitions.go | 2 +- 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index c1f9e4244..6b286bb4a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4407,7 +4407,7 @@ func (r *rpcServer) FetchSupplyLeaves(ctx context.Context, var subtrees supplycommit.SupplyTrees if needsInclusionProofs { subtreeResult, err := r.cfg.SupplyCommitManager.FetchSubTrees( - ctx, assetSpec, + ctx, assetSpec, fn.None[uint32](), ) if err != nil { return nil, fmt.Errorf("failed to fetch subtrees for "+ diff --git a/tapdb/supply_tree.go b/tapdb/supply_tree.go index 8a0eb6164..3134aa976 100644 --- a/tapdb/supply_tree.go +++ b/tapdb/supply_tree.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/mssmt" "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" @@ -196,10 +197,108 @@ func fetchSubTreeInternal(ctx context.Context, db BaseUniverseStore, return memTree, nil } -// FetchSubTrees returns copies of all sub-trees (mint, burn, ignore) for the +// filterSubTree applies filtering to the leaves of a subtree. +func filterSubTree(ctx context.Context, + treeType supplycommit.SupplySubTree, subTree mssmt.Tree, + blockHeightEnd fn.Option[uint32]) (mssmt.Tree, error) { + + if blockHeightEnd.IsNone() { + // No filtering needed, return the original tree. + return subTree, nil + } + + // Create a new in-memory tree to copy into. + filteredSubTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore()) + + // Create a predicate function to filter leaves based on block height. + filterPredicate := func(key [32]byte, leaf mssmt.LeafNode) (bool, + error) { + + blockHeightEndVal, err := blockHeightEnd.UnwrapOrErr( + fmt.Errorf("block height end not set"), + ) + if err != nil { + return false, err + } + + // Decode the leaf based on the tree type to extract block + // height. + switch treeType { + case supplycommit.MintTreeType: + // For mint trees, decode mint event to get block + // height. + var mintEvent supplycommit.NewMintEvent + err := mintEvent.Decode(bytes.NewReader(leaf.Value)) + if err != nil { + return false, fmt.Errorf("unable to decode "+ + "mint event: %w", err) + } + + // Extract block height directly from the mint event. + mintBlockHeight := mintEvent.MintHeight + + // Include the leaf if it's within range. + return mintBlockHeight <= blockHeightEndVal, nil + + case supplycommit.BurnTreeType: + // For burn trees, decode burn leaf to get block height. + var burnLeaf universe.BurnLeaf + err := burnLeaf.Decode(bytes.NewReader(leaf.Value)) + if err != nil { + return false, fmt.Errorf("unable to decode "+ + "burn leaf: %w", err) + } + + // Extract block height directly from the burn proof. + proofBlockHeight := burnLeaf.BurnProof.BlockHeight + + // Include the leaf if it's within range. + return proofBlockHeight <= blockHeightEndVal, nil + + case supplycommit.IgnoreTreeType: + // For ignore trees, decode signed ignore tuple to get + // block height. + var signedIgnoreTuple universe.SignedIgnoreTuple + err := signedIgnoreTuple.Decode( + bytes.NewReader(leaf.Value), + ) + if err != nil { + return false, fmt.Errorf("unable to decode "+ + "signed ignore tuple: %w", err) + } + + // Extract block height directly from the "ignore" + // tuple. + tupleBlockHeight := + signedIgnoreTuple.IgnoreTuple.Val.BlockHeight + + // Include the leaf if it's within range. + return tupleBlockHeight <= blockHeightEndVal, nil + + default: + return false, fmt.Errorf("unknown tree type: %v", + treeType) + } + } + + // Copy the persistent tree to the in-memory tree with filtering. + err := subTree.CopyFilter(ctx, filteredSubTree, filterPredicate) + if err != nil { + return nil, fmt.Errorf("unable to copy "+ + "sub-tree: %w", err) + } + + return filteredSubTree, nil +} + +// FetchSubTrees returns copies of all subtrees (mint, burn, ignore) for the // given asset spec. +// +// If blockHeightEnd is specified, only leaves with a block height less than +// or equal to the given height are included in the returned subtrees. func (s *SupplyTreeStore) FetchSubTrees(ctx context.Context, - spec asset.Specifier) lfn.Result[supplycommit.SupplyTrees] { + spec asset.Specifier, + blockHeightEnd fn.Option[uint32]) lfn.Result[supplycommit.SupplyTrees] { groupKey, err := spec.UnwrapGroupKeyOrErr() if err != nil { @@ -222,7 +321,15 @@ func (s *SupplyTreeStore) FetchSubTrees(ctx context.Context, "sub-tree %v: %w", treeType, fetchErr) } - trees[treeType] = subTree + filteredSubTree, err := filterSubTree( + ctx, treeType, subTree, blockHeightEnd, + ) + if err != nil { + return fmt.Errorf("failed to filter "+ + "sub-tree %v: %w", treeType, err) + } + + trees[treeType] = filteredSubTree } return nil }) diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index e14f947c7..6e7fdab09 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -262,9 +262,10 @@ type SupplyTreeView interface { FetchSubTree(ctx context.Context, assetSpec asset.Specifier, treeType SupplySubTree) lfn.Result[mssmt.Tree] - // FetchSubTrees returns all the sub trees for the given asset spec. + // FetchSubTrees returns all the subtrees for the given asset spec. FetchSubTrees(ctx context.Context, - assetSpec asset.Specifier) lfn.Result[SupplyTrees] + assetSpec asset.Specifier, + blockHeightEnd fn.Option[uint32]) lfn.Result[SupplyTrees] // FetchRootSupplyTree returns the root supply tree which contains a // commitment to each of the sub trees. diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index fe0c0bbe1..0f820faab 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -531,7 +531,9 @@ func (m *Manager) FetchCommitment(ctx context.Context, "supply tree: %w", err) } - subtrees, err := m.cfg.TreeView.FetchSubTrees(ctx, assetSpec).Unpack() + subtrees, err := m.cfg.TreeView.FetchSubTrees( + ctx, assetSpec, fn.None[uint32](), + ).Unpack() if err != nil { return zero, fmt.Errorf("unable to fetch supply commit sub "+ "trees: %w", err) @@ -565,11 +567,14 @@ func (m *Manager) FetchSupplyLeavesByHeight( // FetchSubTrees returns all the sub trees for the given asset specifier. func (m *Manager) FetchSubTrees(ctx context.Context, - assetSpec asset.Specifier) (SupplyTrees, error) { + assetSpec asset.Specifier, + blockHeightEnd fn.Option[uint32]) (SupplyTrees, error) { var zero SupplyTrees - subtrees, err := m.cfg.TreeView.FetchSubTrees(ctx, assetSpec).Unpack() + subtrees, err := m.cfg.TreeView.FetchSubTrees( + ctx, assetSpec, blockHeightEnd, + ).Unpack() if err != nil { return zero, fmt.Errorf("unable to fetch sub trees: %w", err) } diff --git a/universe/supplycommit/mock.go b/universe/supplycommit/mock.go index 52ff11a82..13c9f2e2a 100644 --- a/universe/supplycommit/mock.go +++ b/universe/supplycommit/mock.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/mssmt" "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapsend" @@ -36,7 +37,8 @@ func (m *mockSupplyTreeView) FetchSubTree(_ context.Context, } func (m *mockSupplyTreeView) FetchSubTrees(_ context.Context, - assetSpec asset.Specifier) lfn.Result[SupplyTrees] { + assetSpec asset.Specifier, + blockHeightEnd fn.Option[uint32]) lfn.Result[SupplyTrees] { args := m.Called(assetSpec) return args.Get(0).(lfn.Result[SupplyTrees]) diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index fe4f3ccd2..4f1e993c1 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -372,7 +372,7 @@ func (c *CommitTreeCreateState) ProcessEvent(event Event, // // TODO(roasbeef): sanity check on population of map? oldSupplyTrees, err := env.TreeView.FetchSubTrees( - ctx, env.AssetSpec, + ctx, env.AssetSpec, fn.None[uint32](), ).Unpack() if err != nil { return nil, fmt.Errorf("unable to fetch old sub "+ From 8081f0fb7f0acb70668d6e305c589eb5dd9e8353 Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 25 Aug 2025 15:17:29 +0100 Subject: [PATCH 30/51] supplyverifier: add FetchCommitment method Add a method to retrieve supply commitments from the verifier using a supply commitment locator. Locator supported options are: the first supply commit, the supply commit committed to at a given outpoint, and the supply commit that spends a given outpoint. A follow-up commit will remove FetchCommitment from the supply commit manager in favor of this new method. --- universe/supplyverifier/env.go | 15 ++ universe/supplyverifier/manager.go | 241 +++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go index 58616cdec..013ec3400 100644 --- a/universe/supplyverifier/env.go +++ b/universe/supplyverifier/env.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/mssmt" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe/supplycommit" @@ -63,11 +64,25 @@ type SupplyCommitView interface { leaves supplycommit.SupplyLeaves) error } +// SupplyTreeView is an interface that is used to look up the root (upper) +// supply tree, subtrees, and leaves. +// +// nolint: lll type SupplyTreeView interface { // FetchSupplyTrees returns a copy of the root supply tree and subtrees // for the given asset spec. FetchSupplyTrees(ctx context.Context, spec asset.Specifier) (mssmt.Tree, *supplycommit.SupplyTrees, error) + + // FetchSubTrees returns all the subtrees for the given asset spec. + FetchSubTrees(ctx context.Context, assetSpec asset.Specifier, + blockHeightEnd fn.Option[uint32]) lfn.Result[supplycommit.SupplyTrees] + + // FetchSupplyLeavesByHeight fetches all supply leaves for a given asset + // specifier within a given block height range. + FetchSupplyLeavesByHeight(ctx context.Context, spec asset.Specifier, + startHeight, + endHeight uint32) lfn.Result[supplycommit.SupplyLeaves] } // Environment is a struct that holds all the dependencies that the supply diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index 2d03eec6c..339045578 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -7,8 +7,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/mssmt" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe/supplycommit" "github.com/lightningnetwork/lnd/msgmux" @@ -234,6 +236,245 @@ func (m *Manager) InsertSupplyCommit(ctx context.Context, ) } +// SupplyCommitSnapshot packages the on-chain state of a supply commitment at a +// specific block height: the root commitment, the supply tree, +// the subtrees at that height, the new leaves since the previous commitment, +// and the chain proof that links the leaves to the root. +// +// TODO(guggero): Replace call sites that pass three separate params with +// this struct. +type SupplyCommitSnapshot struct { + // Commitment is the root supply commitment that commits to all supply + // leaves up to the block height recorded in CommitmentBlock. + Commitment supplycommit.RootCommitment + + // SupplyTree is the upper supply tree as of CommitmentBlock. + SupplyTree mssmt.Tree + + // Subtrees are the supply subtrees as of CommitmentBlock. + Subtrees supplycommit.SupplyTrees + + // Leaves are the supply leaves added after the previous commitment's + // block height (exclusive) and up to this commitment's block height + // (inclusive). + Leaves supplycommit.SupplyLeaves +} + +// LocatorType is an enum that indicates the type of locator used to identify +// a supply commitment in the database. +type LocatorType uint8 + +const ( + // LocatorTypeOutpoint indicates that the locator type is the outpoint + // of a supply commitment transaction output. + LocatorTypeOutpoint LocatorType = 0 + + // LocatorTypeSpentOutpoint indicates that the locator type is the + // outpoint spent by a supply commitment transaction. + LocatorTypeSpentOutpoint LocatorType = 1 + + // LocatorTypeVeryFirst indicates that the locator type is the very + // first supply commitment transaction output for an asset group. + LocatorTypeVeryFirst LocatorType = 2 +) + +// CommitLocator is used to locate a supply commitment in the database based on +// its on-chain characteristics. +type CommitLocator struct { + // LocatorType indicates the type of locator used to identify the + // supply commitment. + LocatorType LocatorType + + // Outpoint is the outpoint used to locate a supply commitment. + // Depending on the LocatorType, this may be the outpoint created by a + // supply commitment, the outpoint spent by a supply commitment, or an + // empty outpoint for the very first supply commitment of an asset + // group. + Outpoint wire.OutPoint +} + +// BlockHeightRange represents a range of block heights, inclusive of both +// start and end. +type BlockHeightRange struct { + // Start is the starting block height of the range. + Start uint32 + + // End is the ending block height of the range. + End uint32 +} + +// fetchCommitmentBlockRange returns the block height range for fetching supply +// leaves for the given commitment. +// +// The range starts from the block height of the previous commitment +// (exclusive) to the block height of the given commitment (inclusive). If +// there is no previous commitment, the range starts from block height zero. +func (m *Manager) fetchCommitmentBlockRange(ctx context.Context, + assetSpec asset.Specifier, + commitment supplycommit.RootCommitment) (BlockHeightRange, error) { + + var ( + zero BlockHeightRange + view = m.cfg.SupplyCommitView + ) + + commitmentBlock, err := commitment.CommitmentBlock.UnwrapOrErr( + supplycommit.ErrNoBlockInfo, + ) + if err != nil { + return zero, fmt.Errorf("unable to fetch commitment block: %w", + err) + } + + // Determine the block height range for fetching supply leaves. + // + // If there is no preceding commitment, the block height range starts + // from zero. + if commitment.SpentCommitment.IsNone() { + heightRange := BlockHeightRange{ + Start: 0, + End: commitmentBlock.Height, + } + + return heightRange, nil + } + + // Otherwise, we need to fetch the previous commitment to determine + // the starting block height. + prevCommitmentOutPoint, err := commitment.SpentCommitment.UnwrapOrErr( + fmt.Errorf("supply commitment unexpectedly has no spent " + + "outpoint"), + ) + if err != nil { + return zero, err + } + + spentCommitment, err := view.FetchCommitmentByOutpoint( + ctx, assetSpec, prevCommitmentOutPoint, + ) + if err != nil { + return zero, fmt.Errorf("unable to fetch commitment by "+ + "outpoint: %w", err) + } + + spentCommitmentBlock, err := spentCommitment.CommitmentBlock. + UnwrapOrErr(supplycommit.ErrNoBlockInfo) + if err != nil { + return zero, fmt.Errorf("unable to fetch spent commitment "+ + "block: %w", err) + } + + return BlockHeightRange{ + Start: spentCommitmentBlock.Height, + End: commitmentBlock.Height, + }, nil +} + +// FetchCommitment fetches the commitment with the given locator from the local +// database view. +func (m *Manager) FetchCommitment(ctx context.Context, + assetSpec asset.Specifier, locator CommitLocator) (SupplyCommitSnapshot, + error) { + + var ( + zero SupplyCommitSnapshot + err error + + view = m.cfg.SupplyCommitView + commitment *supplycommit.RootCommitment + ) + switch locator.LocatorType { + case LocatorTypeOutpoint: + commitment, err = view.FetchCommitmentByOutpoint( + ctx, assetSpec, locator.Outpoint, + ) + if err != nil { + return zero, fmt.Errorf("unable to fetch commitment "+ + "by outpoint: %w", err) + } + + case LocatorTypeSpentOutpoint: + commitment, err = view.FetchCommitmentBySpentOutpoint( + ctx, assetSpec, locator.Outpoint, + ) + if err != nil { + return zero, fmt.Errorf("unable to fetch commitment "+ + "by spent outpoint: %w", err) + } + + case LocatorTypeVeryFirst: + commitment, err = view.FetchStartingCommitment(ctx, assetSpec) + if err != nil { + return zero, fmt.Errorf("unable to fetch starting "+ + "commitment: %w", err) + } + + default: + return zero, fmt.Errorf("unknown supply commit locator "+ + "type: %d", locator.LocatorType) + } + + // Fetch block height range for fetching supply leaves. + blockHeightRange, err := m.fetchCommitmentBlockRange( + ctx, assetSpec, *commitment, + ) + if err != nil { + return zero, fmt.Errorf("unable to fetch block height "+ + "range: %w", err) + } + + leaves, err := m.cfg.SupplyTreeView.FetchSupplyLeavesByHeight( + ctx, assetSpec, blockHeightRange.Start, blockHeightRange.End, + ).Unpack() + if err != nil { + return zero, fmt.Errorf("unable to fetch supply leaves for "+ + "asset specifier %s: %w", assetSpec.String(), err) + } + + // Fetch supply subtrees at block height. + subtrees, err := m.cfg.SupplyTreeView.FetchSubTrees( + ctx, assetSpec, fn.Some(blockHeightRange.End), + ).Unpack() + if err != nil { + return zero, fmt.Errorf("unable to fetch supply subtrees for "+ + "asset specifier %s: %w", assetSpec.String(), err) + } + + // Formulate supply tree at correct height from subtrees. + bareSupplyTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore()) + supplyTree, err := supplycommit.UpdateRootSupplyTree( + ctx, bareSupplyTree, subtrees, + ) + if err != nil { + return zero, fmt.Errorf("unable to formulate supply tree "+ + "for asset specifier %s: %w", assetSpec.String(), err) + } + + // Sanity check that the derived upper supply tree root matches the + // commitment. + expectedSupplyRoot, err := supplyTree.Root(ctx) + if err != nil { + return zero, fmt.Errorf("unable to fetch upper supply tree "+ + "root for asset specifier %s: %w", + assetSpec.String(), err) + } + + expectedRootHash := expectedSupplyRoot.NodeHash() + actualRootHash := commitment.SupplyRoot.NodeHash() + if expectedRootHash != actualRootHash { + return zero, fmt.Errorf("supply root mismatch for asset "+ + "specifier %s: expected %s, got %s", + assetSpec.String(), expectedRootHash, actualRootHash) + } + + return SupplyCommitSnapshot{ + Commitment: *commitment, + SupplyTree: supplyTree, + Subtrees: subtrees, + Leaves: leaves, + }, nil +} + // CanHandle determines if the state machine associated with the given asset // specifier can handle the given message. If a state machine for the asset // group does not exist, it will be created and started. From dc18c740d528b6f6c073c7736d0fd122bc146a33 Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 25 Aug 2025 14:49:18 +0100 Subject: [PATCH 31/51] universerpc: add locator and update response in FetchSupplyCommit RPC Add `locator` field to the FetchSupplyCommit RPC request to specify which supply commit to retrieve. Supported options include: the first supply commit, the supply commit committed to at a given outpoint, and the supply commit that spends a given outpoint. Also update the response message for this endpoint. The response now includes all leaves new to the specified supply commit, as well as the outpoint that the fetched supply commit spends. --- itest/assertions.go | 47 +- itest/supply_commit_mint_burn_test.go | 31 +- itest/supply_commit_test.go | 29 +- rpcserver.go | 160 ++-- taprpc/universerpc/universe.pb.go | 1033 ++++++++++++---------- taprpc/universerpc/universe.proto | 75 +- taprpc/universerpc/universe.swagger.json | 102 ++- 7 files changed, 857 insertions(+), 620 deletions(-) diff --git a/itest/assertions.go b/itest/assertions.go index a2038378e..2e746748b 100644 --- a/itest/assertions.go +++ b/itest/assertions.go @@ -2786,8 +2786,9 @@ func UpdateAndMineSupplyCommit(t *testing.T, ctx context.Context, // it when the specified condition is met. func WaitForSupplyCommit(t *testing.T, ctx context.Context, tapd unirpc.UniverseClient, groupKeyBytes []byte, + spentCommitOutpoint fn.Option[wire.OutPoint], condition func(*unirpc.FetchSupplyCommitResponse) bool, -) *unirpc.FetchSupplyCommitResponse { +) (*unirpc.FetchSupplyCommitResponse, wire.OutPoint) { groupKeyReq := &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ GroupKeyBytes: groupKeyBytes, @@ -2796,12 +2797,30 @@ func WaitForSupplyCommit(t *testing.T, ctx context.Context, var fetchResp *unirpc.FetchSupplyCommitResponse var err error - require.Eventually(t, func() bool { - fetchResp, err = tapd.FetchSupplyCommit( - ctx, &unirpc.FetchSupplyCommitRequest{ - GroupKey: groupKeyReq, + // By default, we start the fetch from the very first commitment. + // If a spent outpoint is given, we start from there. + req := &unirpc.FetchSupplyCommitRequest{ + GroupKey: groupKeyReq, + Locator: &unirpc.FetchSupplyCommitRequest_VeryFirst{ + VeryFirst: true, + }, + } + + // nolint: lll + spentCommitOutpoint.WhenSome(func(outPoint wire.OutPoint) { + req = &unirpc.FetchSupplyCommitRequest{ + GroupKey: groupKeyReq, + Locator: &unirpc.FetchSupplyCommitRequest_SpentCommitOutpoint{ + SpentCommitOutpoint: &taprpc.OutPoint{ + Txid: outPoint.Hash[:], + OutputIndex: outPoint.Index, + }, }, - ) + } + }) + + require.Eventually(t, func() bool { + fetchResp, err = tapd.FetchSupplyCommit(ctx, req) if err != nil { return false } @@ -2809,5 +2828,19 @@ func WaitForSupplyCommit(t *testing.T, ctx context.Context, return fetchResp != nil && condition(fetchResp) }, defaultWaitTimeout, time.Second) - return fetchResp + // Return the supply commit outpoint used to fetch the next supply + // commitment. The next commitment is retrieved by referencing the + // outpoint of the previously spent commitment. + require.NotNil(t, fetchResp) + + var msgTx wire.MsgTx + err = msgTx.Deserialize(bytes.NewReader(fetchResp.ChainData.Txn)) + require.NoError(t, err) + + supplyCommitOutpoint := wire.OutPoint{ + Hash: msgTx.TxHash(), + Index: fetchResp.ChainData.TxOutIdx, + } + + return fetchResp, supplyCommitOutpoint } diff --git a/itest/supply_commit_mint_burn_test.go b/itest/supply_commit_mint_burn_test.go index 13319c4ee..0b9bc4c11 100644 --- a/itest/supply_commit_mint_burn_test.go +++ b/itest/supply_commit_mint_burn_test.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" taprootassets "github.com/lightninglabs/taproot-assets" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/taprpc" @@ -48,18 +49,19 @@ func testSupplyCommitMintBurn(t *harnessTest) { // Update the on-chain supply commitment for the asset group. // // TODO(roasbeef): still rely on the time based ticker here? - t.Log("Updating and mining supply commitment for asset group") + t.Log("Create first supply commitment tx for asset group") UpdateAndMineSupplyCommit( t.t, ctxb, t.tapd, t.lndHarness.Miner().Client, groupKeyBytes, 1, ) // Fetch the latest supply commitment for the asset group. - t.Log("Fetching supply commitment to verify mint leaves") - fetchResp := WaitForSupplyCommit( - t.t, ctxb, t.tapd, groupKeyBytes, + t.Log("Fetching first supply commitment to verify mint leaves") + fetchResp, supplyOutpoint := WaitForSupplyCommit( + t.t, ctxb, t.tapd, groupKeyBytes, fn.None[wire.OutPoint](), func(resp *unirpc.FetchSupplyCommitResponse) bool { - return resp.BlockHeight > 0 && len(resp.BlockHash) > 0 + return resp.ChainData.BlockHeight > 0 && + len(resp.ChainData.BlockHash) > 0 }, ) @@ -72,7 +74,7 @@ func testSupplyCommitMintBurn(t *harnessTest) { // Verify the issuance leaf inclusion in the supply tree. AssertSubtreeInclusionProof( - t, fetchResp.SupplyCommitmentRoot.RootHash, + t, fetchResp.ChainData.SupplyRootHash, fetchResp.IssuanceSubtreeRoot, ) @@ -106,8 +108,6 @@ func testSupplyCommitMintBurn(t *harnessTest) { ) t.Log("Updating supply commitment after second mint") - - // Update and mine the supply commitment after second mint. UpdateAndMineSupplyCommit( t.t, ctxb, t.tapd, t.lndHarness.Miner().Client, groupKeyBytes, 1, @@ -119,8 +119,8 @@ func testSupplyCommitMintBurn(t *harnessTest) { expectedTotal := int64( mintReq.Asset.Amount + secondMintReq.Asset.Amount, ) - fetchResp = WaitForSupplyCommit( - t.t, ctxb, t.tapd, groupKeyBytes, + fetchResp, supplyOutpoint = WaitForSupplyCommit( + t.t, ctxb, t.tapd, groupKeyBytes, fn.Some(supplyOutpoint), func(resp *unirpc.FetchSupplyCommitResponse) bool { return resp.IssuanceSubtreeRoot != nil && resp.IssuanceSubtreeRoot.RootNode.RootSum == expectedTotal //nolint:lll @@ -175,7 +175,8 @@ func testSupplyCommitMintBurn(t *harnessTest) { t.Log("Verifying supply tree includes burn leaves") // Fetch and verify the supply tree now includes burn leaves. - fetchResp = WaitForSupplyCommit(t.t, ctxb, t.tapd, groupKeyBytes, + fetchResp, _ = WaitForSupplyCommit( + t.t, ctxb, t.tapd, groupKeyBytes, fn.Some(supplyOutpoint), func(resp *unirpc.FetchSupplyCommitResponse) bool { return resp.BurnSubtreeRoot != nil && resp.BurnSubtreeRoot.RootNode.RootSum == int64(burnAmt) //nolint:lll @@ -184,7 +185,7 @@ func testSupplyCommitMintBurn(t *harnessTest) { // Verify the burn subtree inclusion in the supply tree. AssertSubtreeInclusionProof( - t, fetchResp.SupplyCommitmentRoot.RootHash, + t, fetchResp.ChainData.SupplyRootHash, fetchResp.BurnSubtreeRoot, ) @@ -234,16 +235,16 @@ func testSupplyCommitMintBurn(t *harnessTest) { block := finalMinedBlocks[0] blockHash, _ := t.lndHarness.Miner().GetBestBlock() - fetchBlockHash, err := chainhash.NewHash(fetchResp.BlockHash) + fetchBlockHash, err := chainhash.NewHash(fetchResp.ChainData.BlockHash) require.NoError(t.t, err) require.True(t.t, fetchBlockHash.IsEqual(blockHash)) // Re-compute the supply commitment root hash from the latest fetch, // then use that to derive the expected commitment output. supplyCommitRootHash := fn.ToArray[[32]byte]( - fetchResp.SupplyCommitmentRoot.RootHash, + fetchResp.ChainData.SupplyRootHash, ) - internalKey, err := btcec.ParsePubKey(fetchResp.AnchorTxOutInternalKey) + internalKey, err := btcec.ParsePubKey(fetchResp.ChainData.InternalKey) require.NoError(t.t, err) expectedTxOut, _, err := supplycommit.RootCommitTxOut( internalKey, nil, supplyCommitRootHash, diff --git a/itest/supply_commit_test.go b/itest/supply_commit_test.go index 95dd0f448..665e5872c 100644 --- a/itest/supply_commit_test.go +++ b/itest/supply_commit_test.go @@ -247,11 +247,13 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ GroupKeyBytes: groupKeyBytes, }, + Locator: &unirpc.FetchSupplyCommitRequest_VeryFirst{ + VeryFirst: true, + }, }, ) require.Nil(t.t, fetchRespNil) - require.ErrorContains(t.t, err, "supply commitment not found for "+ - "asset group with key") + require.ErrorContains(t.t, err, "commitment not found") t.Log("Update on-chain supply commitment for asset group") @@ -281,6 +283,9 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ GroupKeyBytes: groupKeyBytes, }, + Locator: &unirpc.FetchSupplyCommitRequest_VeryFirst{ + VeryFirst: true, + }, }, ) require.NoError(t.t, err) @@ -288,19 +293,25 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { // If the fetch response has no block height or hash, // it means that the supply commitment transaction has not // been mined yet, so we should retry. - if fetchResp.BlockHeight == 0 || len(fetchResp.BlockHash) == 0 { + if fetchResp.ChainData.BlockHeight == 0 || + len(fetchResp.ChainData.BlockHash) == 0 { + return false } // Once the ignore tree includes the ignored asset outpoint, we // know that the supply commitment has been updated. + if fetchResp.IgnoreSubtreeRoot == nil { + return false + } + return fetchResp.IgnoreSubtreeRoot.RootNode.RootSum == int64(sendAssetAmount+sendChangeAmount) }, defaultWaitTimeout, time.Second) // Verify that the supply commitment tree commits to the ignore subtree. supplyCommitRootHash := fn.ToArray[[32]byte]( - fetchResp.SupplyCommitmentRoot.RootHash, + fetchResp.ChainData.SupplyRootHash, ) // Formulate the ignore leaf node as it should appear in the supply @@ -369,18 +380,18 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { // Ensure that the block hash and height matches the values in the fetch // response. - fetchBlockHash, err := chainhash.NewHash(fetchResp.BlockHash) + fetchBlockHash, err := chainhash.NewHash(fetchResp.ChainData.BlockHash) require.NoError(t.t, err) require.True(t.t, fetchBlockHash.IsEqual(blockHash)) - require.EqualValues(t.t, blockHeight, fetchResp.BlockHeight) + require.EqualValues(t.t, blockHeight, fetchResp.ChainData.BlockHeight) // We expect two transactions in the block: // 1. The supply commitment transaction. // 2. The coinbase transaction. require.Len(t.t, block.Transactions, 2) - internalKey, err := btcec.ParsePubKey(fetchResp.AnchorTxOutInternalKey) + internalKey, err := btcec.ParsePubKey(fetchResp.ChainData.InternalKey) require.NoError(t.t, err) expectedTxOut, _, err := supplycommit.RootCommitTxOut( @@ -415,7 +426,9 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { } require.True(t.t, foundCommitTxOut) - require.EqualValues(t.t, actualBlockTxIndex, fetchResp.BlockTxIndex) + require.EqualValues( + t.t, actualBlockTxIndex, fetchResp.ChainData.TxIndex, + ) // If we try to ignore the same asset outpoint using the secondary // node, it should fail because the secondary node does not have access diff --git a/rpcserver.go b/rpcserver.go index 6b286bb4a..83d5a2034 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -57,6 +57,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapsend" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" "github.com/lightningnetwork/lnd/build" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" @@ -4174,8 +4175,9 @@ func inclusionProofs(ctx context.Context, tree mssmt.Tree, return proofs, nil } -// supplySubtreeRoot fetches the root of a specific supply subtree and its -// supply tree inclusion proof. +// supplySubtreeRoot formulates an inclusion proof for a supply subtree. +// The inclusion proof can be used to verify that the subtree root is indeed +// part of the supply commitment root. func supplySubtreeRoot(ctx context.Context, supplyTree mssmt.Tree, subtrees supplycommit.SupplyTrees, subtreeType supplycommit.SupplySubTree) ( @@ -4231,35 +4233,38 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, // Formulate an asset specifier from the asset group key. assetSpec := asset.NewSpecifierFromGroupKey(*groupPubKey) + locator, err := unmarshalCommitLocator( + req.GetCommitOutpoint(), req.GetSpentCommitOutpoint(), + req.GetVeryFirst(), + ) + if err != nil { + return nil, fmt.Errorf("failed to parse commitment "+ + "locator: %w", err) + } + // Fetch the supply commitment for the asset specifier. - respOpt, err := r.cfg.SupplyCommitManager.FetchCommitment( - ctx, assetSpec, + commit, err := r.cfg.SupplyVerifyManager.FetchCommitment( + ctx, assetSpec, locator, ) if err != nil { return nil, fmt.Errorf("failed to fetch supply commit: %w", err) } - if respOpt.IsNone() { - return nil, fmt.Errorf("supply commitment not found for "+ - "asset group with key %x", - groupPubKey.SerializeCompressed()) - } - resp, err := respOpt.UnwrapOrErr(fmt.Errorf("unexpected None value " + - "for supply commitment response")) - if err != nil { - return nil, err - } + rootCommit := commit.Commitment - supplyTreeRoot, err := resp.SupplyTree.Root(ctx) + issuanceLeaves, burnLeaves, ignoreLeaves, err := marshalSupplyLeaves( + commit.Leaves, + ) if err != nil { - return nil, fmt.Errorf("failed to get supply tree root: %w", + return nil, fmt.Errorf("unable to marshal supply leaves: %w", err) } // Fetch subtree commitment root and inclusion proofs for the issuance // subtree. rpcIssuanceSubtreeRoot, err := supplySubtreeRoot( - ctx, resp.SupplyTree, resp.Subtrees, supplycommit.MintTreeType, + ctx, commit.SupplyTree, commit.Subtrees, + supplycommit.MintTreeType, ) if err != nil { return nil, fmt.Errorf("failed to fetch supply issuance "+ @@ -4269,7 +4274,8 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, // Fetch subtree commitment root and inclusion proofs for the burn // subtree. rpcBurnSubtreeRoot, err := supplySubtreeRoot( - ctx, resp.SupplyTree, resp.Subtrees, supplycommit.BurnTreeType, + ctx, commit.SupplyTree, commit.Subtrees, + supplycommit.BurnTreeType, ) if err != nil { return nil, fmt.Errorf("failed to fetch supply burn subtree "+ @@ -4279,7 +4285,7 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, // Fetch subtree commitment root and inclusion proofs for the ignore // subtree. rpcIgnoreSubtreeRoot, err := supplySubtreeRoot( - ctx, resp.SupplyTree, resp.Subtrees, + ctx, commit.SupplyTree, commit.Subtrees, supplycommit.IgnoreTreeType, ) if err != nil { @@ -4287,51 +4293,57 @@ func (r *rpcServer) FetchSupplyCommit(ctx context.Context, "root: %w", err) } - // Sanity check: ensure the supply root derived from the supply tree - // matches the root provided in the chain commitment. - if resp.ChainCommitment.SupplyRoot.NodeHash() != - supplyTreeRoot.NodeHash() { + // Create a chain proof from the commitment block data. + commitBlock, err := rootCommit.CommitmentBlock.UnwrapOrErr( + fmt.Errorf("commitment block not found"), + ) + if err != nil { + return nil, err + } - return nil, fmt.Errorf("mismatched supply commitment root: "+ - "expected %x, got %x", - resp.ChainCommitment.SupplyRoot.NodeHash(), - supplyTreeRoot.NodeHash()) + // Sanity check commitment block data. + if commitBlock.BlockHeader == nil || commitBlock.MerkleProof == nil { + return nil, fmt.Errorf("commitment block data is incomplete") } - txOutInternalKey := resp.ChainCommitment.InternalKey.PubKey + chainProof := supplycommit.ChainProof{ + Header: *commitBlock.BlockHeader, + BlockHeight: commitBlock.Height, + MerkleProof: *commitBlock.MerkleProof, + TxIndex: commitBlock.TxIndex, + } - // Extract the block height and hash from the chain commitment if - // present. - var ( - blockHeight uint32 - blockHash []byte - txIndex uint32 - chainFees int64 - ) - resp.ChainCommitment.CommitmentBlock.WhenSome( - func(b supplycommit.CommitmentBlock) { - blockHeight = b.Height - blockHash = b.Hash[:] - txIndex = b.TxIndex - chainFees = b.ChainFees + // Marshal the chain data using the existing function. + chainData, err := marshalSupplyCommitChainData(rootCommit, chainProof) + if err != nil { + return nil, fmt.Errorf("failed to marshal supply commit "+ + "chain data: %w", err) + } + + // Marshal the spent commitment outpoint if available. + var spentCommitmentOutpoint *taprpc.OutPoint + rootCommit.SpentCommitment.WhenSome( + func(outpoint wire.OutPoint) { + spentCommitmentOutpoint = &taprpc.OutPoint{ + Txid: outpoint.Hash[:], + OutputIndex: outpoint.Index, + } }, ) return &unirpc.FetchSupplyCommitResponse{ - SupplyCommitmentRoot: marshalMssmtNode(supplyTreeRoot), - - AnchorTxid: resp.ChainCommitment.Txn.TxID(), - AnchorTxOutIdx: resp.ChainCommitment.TxOutIdx, - AnchorTxOutInternalKey: txOutInternalKey.SerializeCompressed(), - - BlockHeight: blockHeight, - BlockHash: blockHash, - BlockTxIndex: txIndex, - TxChainFeesSats: chainFees, + ChainData: chainData, + TxChainFeesSats: commitBlock.ChainFees, IssuanceSubtreeRoot: rpcIssuanceSubtreeRoot, BurnSubtreeRoot: rpcBurnSubtreeRoot, IgnoreSubtreeRoot: rpcIgnoreSubtreeRoot, + + IssuanceLeaves: issuanceLeaves, + BurnLeaves: burnLeaves, + IgnoreLeaves: ignoreLeaves, + + SpentCommitmentOutpoint: spentCommitmentOutpoint, }, nil } @@ -6973,6 +6985,50 @@ func unmarshalGroupKey(groupKeyBytes []byte, } } +// unmarshalCommitLocator attempts to parse a commitment locator from the +// RPC form. +func unmarshalCommitLocator(outpoint, spentOutpoint *taprpc.OutPoint, + veryFirst bool) (supplyverifier.CommitLocator, error) { + + var zero supplyverifier.CommitLocator + + // These values come from a gRPC `oneof` field, so only one can be set + // at a time. + switch { + case outpoint != nil: + op, err := rpcutils.UnmarshalOutPoint(outpoint) + if err != nil { + return zero, fmt.Errorf("error parsing outpoint: %w", + err) + } + + return supplyverifier.CommitLocator{ + LocatorType: supplyverifier.LocatorTypeOutpoint, + Outpoint: op, + }, nil + + case spentOutpoint != nil: + op, err := rpcutils.UnmarshalOutPoint(spentOutpoint) + if err != nil { + return zero, fmt.Errorf("error parsing spent "+ + "outpoint: %w", err) + } + + return supplyverifier.CommitLocator{ + LocatorType: supplyverifier.LocatorTypeSpentOutpoint, + Outpoint: op, + }, nil + + case veryFirst: + return supplyverifier.CommitLocator{ + LocatorType: supplyverifier.LocatorTypeVeryFirst, + }, nil + + default: + return zero, fmt.Errorf("commitment locator must be set") + } +} + // unmarshalAssetLeaf unmarshals an asset leaf from the RPC form. func unmarshalAssetLeaf(leaf *unirpc.AssetLeaf) (*universe.Leaf, error) { // We'll just pull the asset details from the serialized issuance proof diff --git a/taprpc/universerpc/universe.pb.go b/taprpc/universerpc/universe.pb.go index eef2ba9f6..a7d41fe62 100644 --- a/taprpc/universerpc/universe.pb.go +++ b/taprpc/universerpc/universe.pb.go @@ -3670,6 +3670,14 @@ type FetchSupplyCommitRequest struct { // *FetchSupplyCommitRequest_GroupKeyBytes // *FetchSupplyCommitRequest_GroupKeyStr GroupKey isFetchSupplyCommitRequest_GroupKey `protobuf_oneof:"group_key"` + // Specifies which supply commit to fetch. + // + // Types that are assignable to Locator: + // + // *FetchSupplyCommitRequest_CommitOutpoint + // *FetchSupplyCommitRequest_SpentCommitOutpoint + // *FetchSupplyCommitRequest_VeryFirst + Locator isFetchSupplyCommitRequest_Locator `protobuf_oneof:"locator"` } func (x *FetchSupplyCommitRequest) Reset() { @@ -3725,6 +3733,34 @@ func (x *FetchSupplyCommitRequest) GetGroupKeyStr() string { return "" } +func (m *FetchSupplyCommitRequest) GetLocator() isFetchSupplyCommitRequest_Locator { + if m != nil { + return m.Locator + } + return nil +} + +func (x *FetchSupplyCommitRequest) GetCommitOutpoint() *taprpc.OutPoint { + if x, ok := x.GetLocator().(*FetchSupplyCommitRequest_CommitOutpoint); ok { + return x.CommitOutpoint + } + return nil +} + +func (x *FetchSupplyCommitRequest) GetSpentCommitOutpoint() *taprpc.OutPoint { + if x, ok := x.GetLocator().(*FetchSupplyCommitRequest_SpentCommitOutpoint); ok { + return x.SpentCommitOutpoint + } + return nil +} + +func (x *FetchSupplyCommitRequest) GetVeryFirst() bool { + if x, ok := x.GetLocator().(*FetchSupplyCommitRequest_VeryFirst); ok { + return x.VeryFirst + } + return false +} + type isFetchSupplyCommitRequest_GroupKey interface { isFetchSupplyCommitRequest_GroupKey() } @@ -3744,6 +3780,39 @@ func (*FetchSupplyCommitRequest_GroupKeyBytes) isFetchSupplyCommitRequest_GroupK func (*FetchSupplyCommitRequest_GroupKeyStr) isFetchSupplyCommitRequest_GroupKey() {} +type isFetchSupplyCommitRequest_Locator interface { + isFetchSupplyCommitRequest_Locator() +} + +type FetchSupplyCommitRequest_CommitOutpoint struct { + // Fetch the the supply commitment that created this new commitment + // output on chain. + CommitOutpoint *taprpc.OutPoint `protobuf:"bytes,3,opt,name=commit_outpoint,json=commitOutpoint,proto3,oneof"` +} + +type FetchSupplyCommitRequest_SpentCommitOutpoint struct { + // Fetch the supply commitment that spent the specified commitment + // output on chain to create a new supply commitment. This can be used + // to traverse the chain of supply commitments by watching the spend of + // the commitment output. + SpentCommitOutpoint *taprpc.OutPoint `protobuf:"bytes,4,opt,name=spent_commit_outpoint,json=spentCommitOutpoint,proto3,oneof"` +} + +type FetchSupplyCommitRequest_VeryFirst struct { + // Fetch the very first supply commitment for the asset group. This + // returns the initial supply commitment that spent the pre-commitment + // output of the very first asset mint of a grouped asset (also known + // as the group anchor). This is useful as the starting point to fetch + // all supply commitments for a grouped asset one by one. + VeryFirst bool `protobuf:"varint,5,opt,name=very_first,json=veryFirst,proto3,oneof"` +} + +func (*FetchSupplyCommitRequest_CommitOutpoint) isFetchSupplyCommitRequest_Locator() {} + +func (*FetchSupplyCommitRequest_SpentCommitOutpoint) isFetchSupplyCommitRequest_Locator() {} + +func (*FetchSupplyCommitRequest_VeryFirst) isFetchSupplyCommitRequest_Locator() {} + type SupplyCommitSubtreeRoot struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3825,33 +3894,34 @@ type FetchSupplyCommitResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The supply commitment merkle sum root node for the specified asset. - SupplyCommitmentRoot *MerkleSumNode `protobuf:"bytes,1,opt,name=supply_commitment_root,json=supplyCommitmentRoot,proto3" json:"supply_commitment_root,omitempty"` - // The txid of the anchor transaction that commits to the supply - // commitment for the specified asset. - AnchorTxid string `protobuf:"bytes,2,opt,name=anchor_txid,json=anchorTxid,proto3" json:"anchor_txid,omitempty"` - // The output index of the anchor transaction that commits to the supply - // commitment for the specified asset. - AnchorTxOutIdx uint32 `protobuf:"varint,3,opt,name=anchor_tx_out_idx,json=anchorTxOutIdx,proto3" json:"anchor_tx_out_idx,omitempty"` - // The transaction output taproot internal key of the anchor transaction - // that commits to the supply commitment for the specified asset. - AnchorTxOutInternalKey []byte `protobuf:"bytes,4,opt,name=anchor_tx_out_internal_key,json=anchorTxOutInternalKey,proto3" json:"anchor_tx_out_internal_key,omitempty"` - // The height of the block at which the supply commitment was anchored. - BlockHeight uint32 `protobuf:"varint,5,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // The hash of the block at which the supply commitment was anchored. - BlockHash []byte `protobuf:"bytes,6,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` - // The index of the transaction in the block that commits to the supply - // commitment. - BlockTxIndex uint32 `protobuf:"varint,7,opt,name=block_tx_index,json=blockTxIndex,proto3" json:"block_tx_index,omitempty"` + // The supply commitment chain data that contains both the commitment and + // chain proof information. + ChainData *SupplyCommitChainData `protobuf:"bytes,1,opt,name=chain_data,json=chainData,proto3" json:"chain_data,omitempty"` // The total number of satoshis in on-chain fees paid by the supply // commitment transaction. - TxChainFeesSats int64 `protobuf:"varint,8,opt,name=tx_chain_fees_sats,json=txChainFeesSats,proto3" json:"tx_chain_fees_sats,omitempty"` + TxChainFeesSats int64 `protobuf:"varint,2,opt,name=tx_chain_fees_sats,json=txChainFeesSats,proto3" json:"tx_chain_fees_sats,omitempty"` // The root of the issuance tree for the specified asset. - IssuanceSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,9,opt,name=issuance_subtree_root,json=issuanceSubtreeRoot,proto3" json:"issuance_subtree_root,omitempty"` + IssuanceSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,3,opt,name=issuance_subtree_root,json=issuanceSubtreeRoot,proto3" json:"issuance_subtree_root,omitempty"` // The root of the burn tree for the specified asset. - BurnSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,10,opt,name=burn_subtree_root,json=burnSubtreeRoot,proto3" json:"burn_subtree_root,omitempty"` + BurnSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,4,opt,name=burn_subtree_root,json=burnSubtreeRoot,proto3" json:"burn_subtree_root,omitempty"` // The root of the ignore tree for the specified asset. - IgnoreSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,11,opt,name=ignore_subtree_root,json=ignoreSubtreeRoot,proto3" json:"ignore_subtree_root,omitempty"` + IgnoreSubtreeRoot *SupplyCommitSubtreeRoot `protobuf:"bytes,5,opt,name=ignore_subtree_root,json=ignoreSubtreeRoot,proto3" json:"ignore_subtree_root,omitempty"` + // The issuance leaves that were added by this supply commitment. Does not + // include leaves that were already present in the issuance subtree before + // the block height at which this supply commitment was anchored. + IssuanceLeaves []*SupplyLeafEntry `protobuf:"bytes,6,rep,name=issuance_leaves,json=issuanceLeaves,proto3" json:"issuance_leaves,omitempty"` + // The burn leaves that were added by this supply commitment. Does not + // include leaves that were already present in the burn subtree before + // the block height at which this supply commitment was anchored. + BurnLeaves []*SupplyLeafEntry `protobuf:"bytes,7,rep,name=burn_leaves,json=burnLeaves,proto3" json:"burn_leaves,omitempty"` + // The ignore leaves that were added by this supply commitment. Does not + // include leaves that were already present in the ignore subtree before + // the block height at which this supply commitment was anchored. + IgnoreLeaves []*SupplyLeafEntry `protobuf:"bytes,8,rep,name=ignore_leaves,json=ignoreLeaves,proto3" json:"ignore_leaves,omitempty"` + // The outpoint of the previous commitment that this new commitment is + // spending. This must be set unless this is the very first supply + // commitment of a grouped asset. + SpentCommitmentOutpoint *taprpc.OutPoint `protobuf:"bytes,9,opt,name=spent_commitment_outpoint,json=spentCommitmentOutpoint,proto3" json:"spent_commitment_outpoint,omitempty"` } func (x *FetchSupplyCommitResponse) Reset() { @@ -3886,79 +3956,65 @@ func (*FetchSupplyCommitResponse) Descriptor() ([]byte, []int) { return file_universerpc_universe_proto_rawDescGZIP(), []int{57} } -func (x *FetchSupplyCommitResponse) GetSupplyCommitmentRoot() *MerkleSumNode { +func (x *FetchSupplyCommitResponse) GetChainData() *SupplyCommitChainData { if x != nil { - return x.SupplyCommitmentRoot + return x.ChainData } return nil } -func (x *FetchSupplyCommitResponse) GetAnchorTxid() string { - if x != nil { - return x.AnchorTxid - } - return "" -} - -func (x *FetchSupplyCommitResponse) GetAnchorTxOutIdx() uint32 { +func (x *FetchSupplyCommitResponse) GetTxChainFeesSats() int64 { if x != nil { - return x.AnchorTxOutIdx + return x.TxChainFeesSats } return 0 } -func (x *FetchSupplyCommitResponse) GetAnchorTxOutInternalKey() []byte { +func (x *FetchSupplyCommitResponse) GetIssuanceSubtreeRoot() *SupplyCommitSubtreeRoot { if x != nil { - return x.AnchorTxOutInternalKey + return x.IssuanceSubtreeRoot } return nil } -func (x *FetchSupplyCommitResponse) GetBlockHeight() uint32 { - if x != nil { - return x.BlockHeight - } - return 0 -} - -func (x *FetchSupplyCommitResponse) GetBlockHash() []byte { +func (x *FetchSupplyCommitResponse) GetBurnSubtreeRoot() *SupplyCommitSubtreeRoot { if x != nil { - return x.BlockHash + return x.BurnSubtreeRoot } return nil } -func (x *FetchSupplyCommitResponse) GetBlockTxIndex() uint32 { +func (x *FetchSupplyCommitResponse) GetIgnoreSubtreeRoot() *SupplyCommitSubtreeRoot { if x != nil { - return x.BlockTxIndex + return x.IgnoreSubtreeRoot } - return 0 + return nil } -func (x *FetchSupplyCommitResponse) GetTxChainFeesSats() int64 { +func (x *FetchSupplyCommitResponse) GetIssuanceLeaves() []*SupplyLeafEntry { if x != nil { - return x.TxChainFeesSats + return x.IssuanceLeaves } - return 0 + return nil } -func (x *FetchSupplyCommitResponse) GetIssuanceSubtreeRoot() *SupplyCommitSubtreeRoot { +func (x *FetchSupplyCommitResponse) GetBurnLeaves() []*SupplyLeafEntry { if x != nil { - return x.IssuanceSubtreeRoot + return x.BurnLeaves } return nil } -func (x *FetchSupplyCommitResponse) GetBurnSubtreeRoot() *SupplyCommitSubtreeRoot { +func (x *FetchSupplyCommitResponse) GetIgnoreLeaves() []*SupplyLeafEntry { if x != nil { - return x.BurnSubtreeRoot + return x.IgnoreLeaves } return nil } -func (x *FetchSupplyCommitResponse) GetIgnoreSubtreeRoot() *SupplyCommitSubtreeRoot { +func (x *FetchSupplyCommitResponse) GetSpentCommitmentOutpoint() *taprpc.OutPoint { if x != nil { - return x.IgnoreSubtreeRoot + return x.SpentCommitmentOutpoint } return nil } @@ -5098,366 +5154,378 @@ var file_universerpc_universe_proto_rawDesc = []byte{ 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x77, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, - 0x74, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, - 0xd6, 0x01, 0x0a, 0x17, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x37, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, - 0x72, 0x6f, 0x6f, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, - 0x65, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x1b, 0x73, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, - 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, 0x65, 0x65, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, - 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x8c, 0x05, 0x0a, 0x19, 0x46, 0x65, 0x74, - 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x16, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, - 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, - 0x64, 0x65, 0x52, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6e, 0x63, 0x68, - 0x6f, 0x72, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, - 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x54, 0x78, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x11, 0x61, 0x6e, 0x63, - 0x68, 0x6f, 0x72, 0x5f, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x54, 0x78, 0x4f, 0x75, - 0x74, 0x49, 0x64, 0x78, 0x12, 0x3a, 0x0a, 0x1a, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x74, - 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, - 0x54, 0x78, 0x4f, 0x75, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, - 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x78, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x54, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x12, 0x74, 0x78, 0x5f, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x78, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x65, 0x65, - 0x73, 0x53, 0x61, 0x74, 0x73, 0x12, 0x58, 0x0a, 0x15, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, - 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x13, 0x69, 0x73, 0x73, 0x75, - 0x61, 0x6e, 0x63, 0x65, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, - 0x50, 0x0a, 0x11, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, - 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, - 0x52, 0x0f, 0x62, 0x75, 0x72, 0x6e, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, - 0x74, 0x12, 0x54, 0x0a, 0x13, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x74, - 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, - 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x11, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x53, 0x75, 0x62, 0x74, - 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0xcd, 0x02, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, - 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, - 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, - 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, - 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, - 0x79, 0x53, 0x74, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x45, 0x6e, 0x64, 0x12, 0x2c, 0x0a, 0x12, - 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, - 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, - 0x63, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75, - 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0c, 0x52, 0x0c, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, - 0x12, 0x28, 0x0a, 0x10, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, - 0x6b, 0x65, 0x79, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x69, 0x67, 0x6e, 0x6f, - 0x72, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x7c, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, 0xbf, 0x01, 0x0a, 0x0f, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, - 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x35, 0x0a, 0x08, 0x6c, 0x65, 0x61, - 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, - 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, - 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x52, - 0x08, 0x6c, 0x65, 0x61, 0x66, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x72, 0x61, 0x77, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x72, 0x61, 0x77, 0x4c, 0x65, 0x61, 0x66, 0x22, 0xa7, 0x03, 0x0a, 0x19, 0x46, 0x65, 0x74, 0x63, - 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, - 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, - 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, - 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x43, - 0x0a, 0x1e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x1b, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, - 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, - 0x6f, 0x66, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, - 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, - 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, - 0x12, 0x3f, 0x0a, 0x1c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x19, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, - 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, - 0x73, 0x22, 0x8e, 0x03, 0x0a, 0x15, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x74, - 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, 0x78, 0x6e, 0x12, 0x1c, 0x0a, - 0x0a, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x08, 0x74, 0x78, 0x4f, 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, - 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, - 0x10, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, - 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x70, 0x70, 0x6c, - 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x53, 0x75, 0x6d, 0x12, - 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, - 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x74, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x72, 0x6b, - 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x22, 0xd2, 0x03, 0x0a, 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, - 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x19, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa8, 0x02, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, 0x68, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, + 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, + 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, + 0x53, 0x74, 0x72, 0x12, 0x3b, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, + 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x01, + 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x46, 0x0a, 0x15, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x48, 0x01, 0x52, 0x13, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x76, 0x65, 0x72, 0x79, + 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x09, + 0x76, 0x65, 0x72, 0x79, 0x46, 0x69, 0x72, 0x73, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x09, 0x0a, 0x07, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, + 0x72, 0x22, 0xd6, 0x01, 0x0a, 0x17, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x37, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, + 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x75, + 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x1b, 0x73, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x18, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x54, 0x72, 0x65, 0x65, 0x49, 0x6e, 0x63, 0x6c, + 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xa4, 0x05, 0x0a, 0x19, 0x46, + 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, + 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2b, 0x0a, 0x12, 0x74, + 0x78, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x73, 0x61, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x78, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x46, 0x65, 0x65, 0x73, 0x53, 0x61, 0x74, 0x73, 0x12, 0x58, 0x0a, 0x15, 0x69, 0x73, 0x73, 0x75, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x13, 0x69, + 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, + 0x6f, 0x74, 0x12, 0x50, 0x0a, 0x11, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, + 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, + 0x6f, 0x6f, 0x74, 0x52, 0x0f, 0x62, 0x75, 0x72, 0x6e, 0x53, 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x54, 0x0a, 0x13, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x73, + 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x75, 0x62, 0x74, + 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x11, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x53, + 0x75, 0x62, 0x74, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, + 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, + 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, + 0x76, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x19, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x17, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, - 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, - 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, - 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, 0x72, 0x6e, - 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x75, 0x72, - 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, - 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x74, 0x22, 0xcd, 0x02, 0x0a, 0x18, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, + 0x0a, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x12, 0x2c, + 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x28, 0x0a, 0x10, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x65, 0x6e, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x45, 0x6e, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x10, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x66, + 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, + 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x62, 0x75, + 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x67, + 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x66, + 0x4b, 0x65, 0x79, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, + 0x79, 0x22, 0x7c, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x4b, + 0x65, 0x79, 0x12, 0x31, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, + 0xbf, 0x01, 0x0a, 0x0f, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x35, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, + 0x79, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x65, + 0x61, 0x66, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x6b, + 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x6c, 0x65, 0x61, 0x66, 0x4e, + 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x6c, 0x65, + 0x61, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x4c, 0x65, 0x61, + 0x66, 0x22, 0xa7, 0x03, 0x0a, 0x19, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x45, 0x0a, 0x0f, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, + 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, + 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, + 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x4c, + 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, + 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, + 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x69, 0x73, 0x73, 0x75, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x1b, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, + 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12, 0x3b, 0x0a, + 0x1a, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x17, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, 0x75, + 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x69, 0x67, + 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x19, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x49, 0x6e, 0x63, 0x6c, + 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x22, 0x8e, 0x03, 0x0a, 0x15, + 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x03, 0x74, 0x78, 0x6e, 0x12, 0x1c, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x6f, 0x75, + 0x74, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x74, 0x78, 0x4f, + 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, + 0x6c, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x53, 0x75, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, + 0x0a, 0x15, 0x74, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, + 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, + 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xd2, 0x03, 0x0a, + 0x19, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, - 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, - 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, 0x73, 0x65, 0x72, - 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, - 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x53, 0x53, - 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x02, - 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x79, 0x6e, 0x63, - 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x53, 0x53, - 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, - 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, 0x01, 0x0a, 0x0e, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x10, - 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, - 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, - 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, 0x52, 0x54, - 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, 0x02, 0x12, 0x16, - 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, - 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, 0x10, 0x04, 0x12, + 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, + 0x19, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x52, 0x17, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x69, + 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0e, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x65, 0x61, 0x76, + 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x62, 0x75, 0x72, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x66, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x75, 0x72, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x76, + 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, + 0x66, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x65, + 0x61, 0x76, 0x65, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, + 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, + 0x59, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, + 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, + 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x10, + 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x10, 0x55, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, + 0x0a, 0x12, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x41, 0x4e, 0x43, 0x45, 0x5f, + 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x46, + 0x55, 0x4c, 0x4c, 0x10, 0x01, 0x2a, 0xd1, 0x01, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x4f, 0x52, 0x54, + 0x5f, 0x42, 0x59, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, + 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x41, 0x4d, 0x45, + 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, + 0x53, 0x45, 0x54, 0x5f, 0x49, 0x44, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, + 0x5f, 0x42, 0x59, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, + 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, + 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x53, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, + 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, + 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, + 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x48, 0x45, 0x49, 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, - 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x53, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x52, - 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x48, 0x45, 0x49, - 0x47, 0x48, 0x54, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, - 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, 0x10, 0x07, 0x2a, - 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, - 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, - 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x10, - 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, - 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x46, - 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, - 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, - 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x42, 0x4c, 0x45, - 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, - 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, - 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, - 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, - 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x75, 0x6e, 0x69, + 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4c, 0x59, 0x10, 0x07, 0x2a, 0x40, 0x0a, 0x0d, 0x53, 0x6f, 0x72, + 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, + 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x53, 0x43, + 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x10, 0x01, 0x2a, 0x5f, 0x0a, 0x0f, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x15, + 0x0a, 0x11, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, + 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, + 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1c, + 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x43, + 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x32, 0xf6, 0x10, 0x0a, + 0x08, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x4d, 0x75, 0x6c, + 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x22, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, + 0x6c, 0x74, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, + 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4e, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0d, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, + 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, + 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x44, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, - 0x61, 0x66, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x4c, 0x65, 0x61, 0x66, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3e, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, - 0x0f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x44, - 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x18, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x49, 0x6e, 0x73, - 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, - 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, - 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, - 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x53, - 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x16, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, - 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x19, - 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, 0x65, 0x74, - 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, - 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x61, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4b, + 0x65, 0x79, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x1a, 0x1f, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, + 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x1d, 0x2e, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, + 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x15, 0x4c, 0x69, + 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x41, 0x64, + 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x65, 0x64, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x65, + 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2a, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, + 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0d, 0x55, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x19, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x50, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x1a, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x12, 0x1f, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, + 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, - 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x49, - 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, - 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, - 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, - 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, - 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, - 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x46, 0x65, 0x64, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x19, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x13, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, + 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x65, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, + 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x25, 0x2e, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, - 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x11, 0x46, 0x65, + 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x12, + 0x25, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, + 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x65, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, + 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, + 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, + 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5617,76 +5685,82 @@ var file_universerpc_universe_proto_depIdxs = []int32{ 53, // 58: universerpc.QueryFederationSyncConfigResponse.asset_sync_configs:type_name -> universerpc.AssetFederationSyncConfig 77, // 59: universerpc.IgnoreAssetOutPointRequest.asset_out_point:type_name -> taprpc.AssetOutPoint 8, // 60: universerpc.IgnoreAssetOutPointResponse.leaf:type_name -> universerpc.MerkleSumNode - 8, // 61: universerpc.SupplyCommitSubtreeRoot.root_node:type_name -> universerpc.MerkleSumNode - 8, // 62: universerpc.FetchSupplyCommitResponse.supply_commitment_root:type_name -> universerpc.MerkleSumNode - 61, // 63: universerpc.FetchSupplyCommitResponse.issuance_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot - 61, // 64: universerpc.FetchSupplyCommitResponse.burn_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot - 61, // 65: universerpc.FetchSupplyCommitResponse.ignore_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot - 16, // 66: universerpc.SupplyLeafKey.outpoint:type_name -> universerpc.Outpoint - 64, // 67: universerpc.SupplyLeafEntry.leaf_key:type_name -> universerpc.SupplyLeafKey - 8, // 68: universerpc.SupplyLeafEntry.leaf_node:type_name -> universerpc.MerkleSumNode - 65, // 69: universerpc.FetchSupplyLeavesResponse.issuance_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 70: universerpc.FetchSupplyLeavesResponse.burn_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 71: universerpc.FetchSupplyLeavesResponse.ignore_leaves:type_name -> universerpc.SupplyLeafEntry - 67, // 72: universerpc.InsertSupplyCommitRequest.chain_data:type_name -> universerpc.SupplyCommitChainData - 78, // 73: universerpc.InsertSupplyCommitRequest.spent_commitment_outpoint:type_name -> taprpc.OutPoint - 65, // 74: universerpc.InsertSupplyCommitRequest.issuance_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 75: universerpc.InsertSupplyCommitRequest.burn_leaves:type_name -> universerpc.SupplyLeafEntry - 65, // 76: universerpc.InsertSupplyCommitRequest.ignore_leaves:type_name -> universerpc.SupplyLeafEntry - 10, // 77: universerpc.AssetRootResponse.UniverseRootsEntry.value:type_name -> universerpc.UniverseRoot - 5, // 78: universerpc.Universe.MultiverseRoot:input_type -> universerpc.MultiverseRootRequest - 7, // 79: universerpc.Universe.AssetRoots:input_type -> universerpc.AssetRootRequest - 12, // 80: universerpc.Universe.QueryAssetRoots:input_type -> universerpc.AssetRootQuery - 14, // 81: universerpc.Universe.DeleteAssetRoot:input_type -> universerpc.DeleteRootQuery - 18, // 82: universerpc.Universe.AssetLeafKeys:input_type -> universerpc.AssetLeafKeysRequest - 9, // 83: universerpc.Universe.AssetLeaves:input_type -> universerpc.ID - 22, // 84: universerpc.Universe.QueryProof:input_type -> universerpc.UniverseKey - 25, // 85: universerpc.Universe.InsertProof:input_type -> universerpc.AssetProof - 26, // 86: universerpc.Universe.PushProof:input_type -> universerpc.PushProofRequest - 28, // 87: universerpc.Universe.Info:input_type -> universerpc.InfoRequest - 31, // 88: universerpc.Universe.SyncUniverse:input_type -> universerpc.SyncRequest - 36, // 89: universerpc.Universe.ListFederationServers:input_type -> universerpc.ListFederationServersRequest - 38, // 90: universerpc.Universe.AddFederationServer:input_type -> universerpc.AddFederationServerRequest - 40, // 91: universerpc.Universe.DeleteFederationServer:input_type -> universerpc.DeleteFederationServerRequest - 33, // 92: universerpc.Universe.UniverseStats:input_type -> universerpc.StatsRequest - 43, // 93: universerpc.Universe.QueryAssetStats:input_type -> universerpc.AssetStatsQuery - 47, // 94: universerpc.Universe.QueryEvents:input_type -> universerpc.QueryEventsRequest - 50, // 95: universerpc.Universe.SetFederationSyncConfig:input_type -> universerpc.SetFederationSyncConfigRequest - 54, // 96: universerpc.Universe.QueryFederationSyncConfig:input_type -> universerpc.QueryFederationSyncConfigRequest - 56, // 97: universerpc.Universe.IgnoreAssetOutPoint:input_type -> universerpc.IgnoreAssetOutPointRequest - 58, // 98: universerpc.Universe.UpdateSupplyCommit:input_type -> universerpc.UpdateSupplyCommitRequest - 60, // 99: universerpc.Universe.FetchSupplyCommit:input_type -> universerpc.FetchSupplyCommitRequest - 63, // 100: universerpc.Universe.FetchSupplyLeaves:input_type -> universerpc.FetchSupplyLeavesRequest - 68, // 101: universerpc.Universe.InsertSupplyCommit:input_type -> universerpc.InsertSupplyCommitRequest - 6, // 102: universerpc.Universe.MultiverseRoot:output_type -> universerpc.MultiverseRootResponse - 11, // 103: universerpc.Universe.AssetRoots:output_type -> universerpc.AssetRootResponse - 13, // 104: universerpc.Universe.QueryAssetRoots:output_type -> universerpc.QueryRootResponse - 15, // 105: universerpc.Universe.DeleteAssetRoot:output_type -> universerpc.DeleteRootResponse - 19, // 106: universerpc.Universe.AssetLeafKeys:output_type -> universerpc.AssetLeafKeyResponse - 21, // 107: universerpc.Universe.AssetLeaves:output_type -> universerpc.AssetLeafResponse - 23, // 108: universerpc.Universe.QueryProof:output_type -> universerpc.AssetProofResponse - 23, // 109: universerpc.Universe.InsertProof:output_type -> universerpc.AssetProofResponse - 27, // 110: universerpc.Universe.PushProof:output_type -> universerpc.PushProofResponse - 29, // 111: universerpc.Universe.Info:output_type -> universerpc.InfoResponse - 34, // 112: universerpc.Universe.SyncUniverse:output_type -> universerpc.SyncResponse - 37, // 113: universerpc.Universe.ListFederationServers:output_type -> universerpc.ListFederationServersResponse - 39, // 114: universerpc.Universe.AddFederationServer:output_type -> universerpc.AddFederationServerResponse - 41, // 115: universerpc.Universe.DeleteFederationServer:output_type -> universerpc.DeleteFederationServerResponse - 42, // 116: universerpc.Universe.UniverseStats:output_type -> universerpc.StatsResponse - 46, // 117: universerpc.Universe.QueryAssetStats:output_type -> universerpc.UniverseAssetStats - 48, // 118: universerpc.Universe.QueryEvents:output_type -> universerpc.QueryEventsResponse - 51, // 119: universerpc.Universe.SetFederationSyncConfig:output_type -> universerpc.SetFederationSyncConfigResponse - 55, // 120: universerpc.Universe.QueryFederationSyncConfig:output_type -> universerpc.QueryFederationSyncConfigResponse - 57, // 121: universerpc.Universe.IgnoreAssetOutPoint:output_type -> universerpc.IgnoreAssetOutPointResponse - 59, // 122: universerpc.Universe.UpdateSupplyCommit:output_type -> universerpc.UpdateSupplyCommitResponse - 62, // 123: universerpc.Universe.FetchSupplyCommit:output_type -> universerpc.FetchSupplyCommitResponse - 66, // 124: universerpc.Universe.FetchSupplyLeaves:output_type -> universerpc.FetchSupplyLeavesResponse - 69, // 125: universerpc.Universe.InsertSupplyCommit:output_type -> universerpc.InsertSupplyCommitResponse - 102, // [102:126] is the sub-list for method output_type - 78, // [78:102] is the sub-list for method input_type - 78, // [78:78] is the sub-list for extension type_name - 78, // [78:78] is the sub-list for extension extendee - 0, // [0:78] is the sub-list for field type_name + 78, // 61: universerpc.FetchSupplyCommitRequest.commit_outpoint:type_name -> taprpc.OutPoint + 78, // 62: universerpc.FetchSupplyCommitRequest.spent_commit_outpoint:type_name -> taprpc.OutPoint + 8, // 63: universerpc.SupplyCommitSubtreeRoot.root_node:type_name -> universerpc.MerkleSumNode + 67, // 64: universerpc.FetchSupplyCommitResponse.chain_data:type_name -> universerpc.SupplyCommitChainData + 61, // 65: universerpc.FetchSupplyCommitResponse.issuance_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot + 61, // 66: universerpc.FetchSupplyCommitResponse.burn_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot + 61, // 67: universerpc.FetchSupplyCommitResponse.ignore_subtree_root:type_name -> universerpc.SupplyCommitSubtreeRoot + 65, // 68: universerpc.FetchSupplyCommitResponse.issuance_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 69: universerpc.FetchSupplyCommitResponse.burn_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 70: universerpc.FetchSupplyCommitResponse.ignore_leaves:type_name -> universerpc.SupplyLeafEntry + 78, // 71: universerpc.FetchSupplyCommitResponse.spent_commitment_outpoint:type_name -> taprpc.OutPoint + 16, // 72: universerpc.SupplyLeafKey.outpoint:type_name -> universerpc.Outpoint + 64, // 73: universerpc.SupplyLeafEntry.leaf_key:type_name -> universerpc.SupplyLeafKey + 8, // 74: universerpc.SupplyLeafEntry.leaf_node:type_name -> universerpc.MerkleSumNode + 65, // 75: universerpc.FetchSupplyLeavesResponse.issuance_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 76: universerpc.FetchSupplyLeavesResponse.burn_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 77: universerpc.FetchSupplyLeavesResponse.ignore_leaves:type_name -> universerpc.SupplyLeafEntry + 67, // 78: universerpc.InsertSupplyCommitRequest.chain_data:type_name -> universerpc.SupplyCommitChainData + 78, // 79: universerpc.InsertSupplyCommitRequest.spent_commitment_outpoint:type_name -> taprpc.OutPoint + 65, // 80: universerpc.InsertSupplyCommitRequest.issuance_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 81: universerpc.InsertSupplyCommitRequest.burn_leaves:type_name -> universerpc.SupplyLeafEntry + 65, // 82: universerpc.InsertSupplyCommitRequest.ignore_leaves:type_name -> universerpc.SupplyLeafEntry + 10, // 83: universerpc.AssetRootResponse.UniverseRootsEntry.value:type_name -> universerpc.UniverseRoot + 5, // 84: universerpc.Universe.MultiverseRoot:input_type -> universerpc.MultiverseRootRequest + 7, // 85: universerpc.Universe.AssetRoots:input_type -> universerpc.AssetRootRequest + 12, // 86: universerpc.Universe.QueryAssetRoots:input_type -> universerpc.AssetRootQuery + 14, // 87: universerpc.Universe.DeleteAssetRoot:input_type -> universerpc.DeleteRootQuery + 18, // 88: universerpc.Universe.AssetLeafKeys:input_type -> universerpc.AssetLeafKeysRequest + 9, // 89: universerpc.Universe.AssetLeaves:input_type -> universerpc.ID + 22, // 90: universerpc.Universe.QueryProof:input_type -> universerpc.UniverseKey + 25, // 91: universerpc.Universe.InsertProof:input_type -> universerpc.AssetProof + 26, // 92: universerpc.Universe.PushProof:input_type -> universerpc.PushProofRequest + 28, // 93: universerpc.Universe.Info:input_type -> universerpc.InfoRequest + 31, // 94: universerpc.Universe.SyncUniverse:input_type -> universerpc.SyncRequest + 36, // 95: universerpc.Universe.ListFederationServers:input_type -> universerpc.ListFederationServersRequest + 38, // 96: universerpc.Universe.AddFederationServer:input_type -> universerpc.AddFederationServerRequest + 40, // 97: universerpc.Universe.DeleteFederationServer:input_type -> universerpc.DeleteFederationServerRequest + 33, // 98: universerpc.Universe.UniverseStats:input_type -> universerpc.StatsRequest + 43, // 99: universerpc.Universe.QueryAssetStats:input_type -> universerpc.AssetStatsQuery + 47, // 100: universerpc.Universe.QueryEvents:input_type -> universerpc.QueryEventsRequest + 50, // 101: universerpc.Universe.SetFederationSyncConfig:input_type -> universerpc.SetFederationSyncConfigRequest + 54, // 102: universerpc.Universe.QueryFederationSyncConfig:input_type -> universerpc.QueryFederationSyncConfigRequest + 56, // 103: universerpc.Universe.IgnoreAssetOutPoint:input_type -> universerpc.IgnoreAssetOutPointRequest + 58, // 104: universerpc.Universe.UpdateSupplyCommit:input_type -> universerpc.UpdateSupplyCommitRequest + 60, // 105: universerpc.Universe.FetchSupplyCommit:input_type -> universerpc.FetchSupplyCommitRequest + 63, // 106: universerpc.Universe.FetchSupplyLeaves:input_type -> universerpc.FetchSupplyLeavesRequest + 68, // 107: universerpc.Universe.InsertSupplyCommit:input_type -> universerpc.InsertSupplyCommitRequest + 6, // 108: universerpc.Universe.MultiverseRoot:output_type -> universerpc.MultiverseRootResponse + 11, // 109: universerpc.Universe.AssetRoots:output_type -> universerpc.AssetRootResponse + 13, // 110: universerpc.Universe.QueryAssetRoots:output_type -> universerpc.QueryRootResponse + 15, // 111: universerpc.Universe.DeleteAssetRoot:output_type -> universerpc.DeleteRootResponse + 19, // 112: universerpc.Universe.AssetLeafKeys:output_type -> universerpc.AssetLeafKeyResponse + 21, // 113: universerpc.Universe.AssetLeaves:output_type -> universerpc.AssetLeafResponse + 23, // 114: universerpc.Universe.QueryProof:output_type -> universerpc.AssetProofResponse + 23, // 115: universerpc.Universe.InsertProof:output_type -> universerpc.AssetProofResponse + 27, // 116: universerpc.Universe.PushProof:output_type -> universerpc.PushProofResponse + 29, // 117: universerpc.Universe.Info:output_type -> universerpc.InfoResponse + 34, // 118: universerpc.Universe.SyncUniverse:output_type -> universerpc.SyncResponse + 37, // 119: universerpc.Universe.ListFederationServers:output_type -> universerpc.ListFederationServersResponse + 39, // 120: universerpc.Universe.AddFederationServer:output_type -> universerpc.AddFederationServerResponse + 41, // 121: universerpc.Universe.DeleteFederationServer:output_type -> universerpc.DeleteFederationServerResponse + 42, // 122: universerpc.Universe.UniverseStats:output_type -> universerpc.StatsResponse + 46, // 123: universerpc.Universe.QueryAssetStats:output_type -> universerpc.UniverseAssetStats + 48, // 124: universerpc.Universe.QueryEvents:output_type -> universerpc.QueryEventsResponse + 51, // 125: universerpc.Universe.SetFederationSyncConfig:output_type -> universerpc.SetFederationSyncConfigResponse + 55, // 126: universerpc.Universe.QueryFederationSyncConfig:output_type -> universerpc.QueryFederationSyncConfigResponse + 57, // 127: universerpc.Universe.IgnoreAssetOutPoint:output_type -> universerpc.IgnoreAssetOutPointResponse + 59, // 128: universerpc.Universe.UpdateSupplyCommit:output_type -> universerpc.UpdateSupplyCommitResponse + 62, // 129: universerpc.Universe.FetchSupplyCommit:output_type -> universerpc.FetchSupplyCommitResponse + 66, // 130: universerpc.Universe.FetchSupplyLeaves:output_type -> universerpc.FetchSupplyLeavesResponse + 69, // 131: universerpc.Universe.InsertSupplyCommit:output_type -> universerpc.InsertSupplyCommitResponse + 108, // [108:132] is the sub-list for method output_type + 84, // [84:108] is the sub-list for method input_type + 84, // [84:84] is the sub-list for extension type_name + 84, // [84:84] is the sub-list for extension extendee + 0, // [0:84] is the sub-list for field type_name } func init() { file_universerpc_universe_proto_init() } @@ -6495,6 +6569,9 @@ func file_universerpc_universe_proto_init() { file_universerpc_universe_proto_msgTypes[55].OneofWrappers = []any{ (*FetchSupplyCommitRequest_GroupKeyBytes)(nil), (*FetchSupplyCommitRequest_GroupKeyStr)(nil), + (*FetchSupplyCommitRequest_CommitOutpoint)(nil), + (*FetchSupplyCommitRequest_SpentCommitOutpoint)(nil), + (*FetchSupplyCommitRequest_VeryFirst)(nil), } file_universerpc_universe_proto_msgTypes[58].OneofWrappers = []any{ (*FetchSupplyLeavesRequest_GroupKeyBytes)(nil), diff --git a/taprpc/universerpc/universe.proto b/taprpc/universerpc/universe.proto index d7236a825..cb240cc0b 100644 --- a/taprpc/universerpc/universe.proto +++ b/taprpc/universerpc/universe.proto @@ -803,6 +803,26 @@ message FetchSupplyCommitRequest { // REST). string group_key_str = 2; } + + // Specifies which supply commit to fetch. + oneof locator { + // Fetch the the supply commitment that created this new commitment + // output on chain. + taprpc.OutPoint commit_outpoint = 3; + + // Fetch the supply commitment that spent the specified commitment + // output on chain to create a new supply commitment. This can be used + // to traverse the chain of supply commitments by watching the spend of + // the commitment output. + taprpc.OutPoint spent_commit_outpoint = 4; + + // Fetch the very first supply commitment for the asset group. This + // returns the initial supply commitment that spent the pre-commitment + // output of the very first asset mint of a grouped asset (also known + // as the group anchor). This is useful as the starting point to fetch + // all supply commitments for a grouped asset one by one. + bool very_first = 5; + } } message SupplyCommitSubtreeRoot { @@ -821,43 +841,42 @@ message SupplyCommitSubtreeRoot { } message FetchSupplyCommitResponse { - // The supply commitment merkle sum root node for the specified asset. - MerkleSumNode supply_commitment_root = 1; - - // The txid of the anchor transaction that commits to the supply - // commitment for the specified asset. - string anchor_txid = 2; - - // The output index of the anchor transaction that commits to the supply - // commitment for the specified asset. - uint32 anchor_tx_out_idx = 3; - - // The transaction output taproot internal key of the anchor transaction - // that commits to the supply commitment for the specified asset. - bytes anchor_tx_out_internal_key = 4; - - // The height of the block at which the supply commitment was anchored. - uint32 block_height = 5; - - // The hash of the block at which the supply commitment was anchored. - bytes block_hash = 6; - - // The index of the transaction in the block that commits to the supply - // commitment. - uint32 block_tx_index = 7; + // The supply commitment chain data that contains both the commitment and + // chain proof information. + SupplyCommitChainData chain_data = 1; // The total number of satoshis in on-chain fees paid by the supply // commitment transaction. - int64 tx_chain_fees_sats = 8; + int64 tx_chain_fees_sats = 2; // The root of the issuance tree for the specified asset. - SupplyCommitSubtreeRoot issuance_subtree_root = 9; + SupplyCommitSubtreeRoot issuance_subtree_root = 3; // The root of the burn tree for the specified asset. - SupplyCommitSubtreeRoot burn_subtree_root = 10; + SupplyCommitSubtreeRoot burn_subtree_root = 4; // The root of the ignore tree for the specified asset. - SupplyCommitSubtreeRoot ignore_subtree_root = 11; + SupplyCommitSubtreeRoot ignore_subtree_root = 5; + + // The issuance leaves that were added by this supply commitment. Does not + // include leaves that were already present in the issuance subtree before + // the block height at which this supply commitment was anchored. + repeated SupplyLeafEntry issuance_leaves = 6; + + // The burn leaves that were added by this supply commitment. Does not + // include leaves that were already present in the burn subtree before + // the block height at which this supply commitment was anchored. + repeated SupplyLeafEntry burn_leaves = 7; + + // The ignore leaves that were added by this supply commitment. Does not + // include leaves that were already present in the ignore subtree before + // the block height at which this supply commitment was anchored. + repeated SupplyLeafEntry ignore_leaves = 8; + + // The outpoint of the previous commitment that this new commitment is + // spending. This must be set unless this is the very first supply + // commitment of a grouped asset. + taprpc.OutPoint spent_commitment_outpoint = 9; } message FetchSupplyLeavesRequest { diff --git a/taprpc/universerpc/universe.swagger.json b/taprpc/universerpc/universe.swagger.json index adc1b6c23..b0b7e1e44 100644 --- a/taprpc/universerpc/universe.swagger.json +++ b/taprpc/universerpc/universe.swagger.json @@ -1539,6 +1539,45 @@ "required": false, "type": "string", "format": "byte" + }, + { + "name": "commit_outpoint.txid", + "description": "Raw bytes representing the transaction id.", + "in": "query", + "required": false, + "type": "string", + "format": "byte" + }, + { + "name": "commit_outpoint.output_index", + "description": "The index of the output on the transaction.", + "in": "query", + "required": false, + "type": "integer", + "format": "int64" + }, + { + "name": "spent_commit_outpoint.txid", + "description": "Raw bytes representing the transaction id.", + "in": "query", + "required": false, + "type": "string", + "format": "byte" + }, + { + "name": "spent_commit_outpoint.output_index", + "description": "The index of the output on the transaction.", + "in": "query", + "required": false, + "type": "integer", + "format": "int64" + }, + { + "name": "very_first", + "description": "Fetch the very first supply commitment for the asset group. This\nreturns the initial supply commitment that spent the pre-commitment\noutput of the very first asset mint of a grouped asset (also known\nas the group anchor). This is useful as the starting point to fetch\nall supply commitments for a grouped asset one by one.", + "in": "query", + "required": false, + "type": "boolean" } ], "tags": [ @@ -2491,38 +2530,9 @@ "universerpcFetchSupplyCommitResponse": { "type": "object", "properties": { - "supply_commitment_root": { - "$ref": "#/definitions/universerpcMerkleSumNode", - "description": "The supply commitment merkle sum root node for the specified asset." - }, - "anchor_txid": { - "type": "string", - "description": "The txid of the anchor transaction that commits to the supply\ncommitment for the specified asset." - }, - "anchor_tx_out_idx": { - "type": "integer", - "format": "int64", - "description": "The output index of the anchor transaction that commits to the supply\ncommitment for the specified asset." - }, - "anchor_tx_out_internal_key": { - "type": "string", - "format": "byte", - "description": "The transaction output taproot internal key of the anchor transaction\nthat commits to the supply commitment for the specified asset." - }, - "block_height": { - "type": "integer", - "format": "int64", - "description": "The height of the block at which the supply commitment was anchored." - }, - "block_hash": { - "type": "string", - "format": "byte", - "description": "The hash of the block at which the supply commitment was anchored." - }, - "block_tx_index": { - "type": "integer", - "format": "int64", - "description": "The index of the transaction in the block that commits to the supply\ncommitment." + "chain_data": { + "$ref": "#/definitions/universerpcSupplyCommitChainData", + "description": "The supply commitment chain data that contains both the commitment and\nchain proof information." }, "tx_chain_fees_sats": { "type": "string", @@ -2540,6 +2550,34 @@ "ignore_subtree_root": { "$ref": "#/definitions/universerpcSupplyCommitSubtreeRoot", "description": "The root of the ignore tree for the specified asset." + }, + "issuance_leaves": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/universerpcSupplyLeafEntry" + }, + "description": "The issuance leaves that were added by this supply commitment. Does not\ninclude leaves that were already present in the issuance subtree before\nthe block height at which this supply commitment was anchored." + }, + "burn_leaves": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/universerpcSupplyLeafEntry" + }, + "description": "The burn leaves that were added by this supply commitment. Does not\ninclude leaves that were already present in the burn subtree before\nthe block height at which this supply commitment was anchored." + }, + "ignore_leaves": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/universerpcSupplyLeafEntry" + }, + "description": "The ignore leaves that were added by this supply commitment. Does not\ninclude leaves that were already present in the ignore subtree before\nthe block height at which this supply commitment was anchored." + }, + "spent_commitment_outpoint": { + "$ref": "#/definitions/taprpcOutPoint", + "description": "The outpoint of the previous commitment that this new commitment is\nspending. This must be set unless this is the very first supply\ncommitment of a grouped asset." } } }, From 7269bb3cfd28673c6798dabb895eae0e159dab67 Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 25 Aug 2025 15:27:36 +0100 Subject: [PATCH 32/51] supplycommit: remove FetchCommitment from manager Favor use of supplyverifier.Manager.FetchCommitment instead. --- universe/supplycommit/manager.go | 49 -------------------------------- 1 file changed, 49 deletions(-) diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index 0f820faab..c9aa96195 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -497,55 +497,6 @@ type FetchCommitmentResp struct { ChainCommitment RootCommitment } -// FetchCommitment fetches the supply commitment for the given asset specifier. -func (m *Manager) FetchCommitment(ctx context.Context, - assetSpec asset.Specifier) (fn.Option[FetchCommitmentResp], error) { - - var zero fn.Option[FetchCommitmentResp] - - chainCommitOpt, err := m.cfg.Commitments.SupplyCommit( - ctx, assetSpec, - ).Unpack() - if err != nil { - return zero, fmt.Errorf("unable to fetch supply commit: %w", - err) - } - - if chainCommitOpt.IsNone() { - // If the chain commitment is not present, we return an empty - // response. - return zero, nil - } - chainCommit, err := chainCommitOpt.UnwrapOrErr( - fmt.Errorf("unable to fetch supply commit: %w", err), - ) - if err != nil { - return zero, err - } - - supplyTree, err := m.cfg.TreeView.FetchRootSupplyTree( - ctx, assetSpec, - ).Unpack() - if err != nil { - return zero, fmt.Errorf("unable to fetch supply commit root "+ - "supply tree: %w", err) - } - - subtrees, err := m.cfg.TreeView.FetchSubTrees( - ctx, assetSpec, fn.None[uint32](), - ).Unpack() - if err != nil { - return zero, fmt.Errorf("unable to fetch supply commit sub "+ - "trees: %w", err) - } - - return fn.Some(FetchCommitmentResp{ - SupplyTree: supplyTree, - Subtrees: subtrees, - ChainCommitment: chainCommit, - }), nil -} - // FetchSupplyLeavesByHeight returns the set of supply leaves for the given // asset specifier within the specified height range. func (m *Manager) FetchSupplyLeavesByHeight( From d14281266f9696f7c3068ebd90377b349ef9c95a Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 26 Aug 2025 21:56:27 +0100 Subject: [PATCH 33/51] itest: extend testSupplyCommitIgnoreAsset to fetch from universe server Extend itest testSupplyCommitIgnoreAsset to fetch the supply commitment from the universe server. The issuer's supply commit state machine should push the commitment to the universe server, which must validate it before serving it. The universe server should only serve the snapshot after successful validation. This change verifies that the commitment can be fetched from the universe server as expected. --- itest/supply_commit_test.go | 94 +++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/itest/supply_commit_test.go b/itest/supply_commit_test.go index 665e5872c..ff1a342eb 100644 --- a/itest/supply_commit_test.go +++ b/itest/supply_commit_test.go @@ -206,18 +206,19 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { _, newIgnoreBlockHeight := t.lndHarness.Miner().GetBestBlock() // Ignore the asset outpoint owned by the secondary node. + ignoreAmt := sendAssetAmount ignoreReq := &unirpc.IgnoreAssetOutPointRequest{ AssetOutPoint: &taprpc.AssetOutPoint{ AnchorOutPoint: transferOutput.Anchor.Outpoint, AssetId: rpcAsset.AssetGenesis.AssetId, ScriptKey: transferOutput.ScriptKey, }, - Amount: sendAssetAmount, + Amount: ignoreAmt, } respIgnore, err := t.tapd.IgnoreAssetOutPoint(ctxb, ignoreReq) require.NoError(t.t, err) require.NotNil(t.t, respIgnore) - require.EqualValues(t.t, sendAssetAmount, respIgnore.Leaf.RootSum) + require.EqualValues(t.t, ignoreAmt, respIgnore.Leaf.RootSum) // We also ignore our change output, so we can later verify that the // proof verifier correctly denies spending the change output. @@ -503,14 +504,87 @@ func testSupplyCommitIgnoreAsset(t *harnessTest) { withError("is ignored"), ) - // TODO(ffranr): The above only tests that the node that issued the - // ignore request has it in its ignore tree and can then deny spending - // it. What we should also test is that the secondary node can sync the - // ignore tree and then also deny spending the ignored asset outpoint - // they received from the primary node. - // Another test case we should add is that a node that _does not_ sync - // the ignore tree can _send_ an ignored asset, but a synced node will - // deny accepting it (transfer will never complete). + t.Log("Fetch first supply commitment from universe server") + // Ensure that the supply commitment was pushed to the universe server + // and that it is retrievable. + var uniFetchResp *unirpc.FetchSupplyCommitResponse + require.Eventually(t.t, func() bool { + // nolint: lll + uniFetchResp, err = t.universeServer.service.FetchSupplyCommit( + ctxb, &unirpc.FetchSupplyCommitRequest{ + GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ + GroupKeyBytes: groupKeyBytes, + }, + Locator: &unirpc.FetchSupplyCommitRequest_VeryFirst{ + VeryFirst: true, + }, + }, + ) + require.NoError(t.t, err) + + // If the fetch response does not include a block height, the + // supply commitment transaction has not been mined yet, so we + // should retry. + if uniFetchResp.ChainData.BlockHeight == 0 { + return false + } + + return true + }, defaultWaitTimeout, time.Second) + + // Assert universe supply commitment fetch response. + require.Len(t.t, uniFetchResp.IssuanceLeaves, 1) + require.Len(t.t, uniFetchResp.BurnLeaves, 0) + require.Len(t.t, uniFetchResp.IgnoreLeaves, 2) + + // Assert issuance leaf properties. + issuanceLeaf := uniFetchResp.IssuanceLeaves[0] + require.EqualValues( + t.t, rpcAsset.Amount, issuanceLeaf.LeafNode.RootSum, + ) + + // Assert ignored leaf properties. + // + // Determine which ignore leaf was the first one we added, so we + // can assert its properties. + firstIgnoreLeaf := uniFetchResp.IgnoreLeaves[0] + secondIgnoreLeaf := uniFetchResp.IgnoreLeaves[1] + if firstIgnoreLeaf.LeafNode.RootSum != int64(ignoreAmt) { + firstIgnoreLeaf, secondIgnoreLeaf = secondIgnoreLeaf, + firstIgnoreLeaf + } + + require.EqualValues(t.t, ignoreAmt, firstIgnoreLeaf.LeafNode.RootSum) + require.EqualValues( + t.t, rpcAsset.Amount-sendAssetAmount, + uint32(secondIgnoreLeaf.LeafNode.RootSum), + ) + + // Assert supply subtree root properties. + require.NotNil(t.t, uniFetchResp.IssuanceSubtreeRoot) + require.NotNil(t.t, uniFetchResp.BurnSubtreeRoot) + require.NotNil(t.t, uniFetchResp.IgnoreSubtreeRoot) + + // Assert that the issuance subtree root sum matches the total + // amount of issued assets. + require.EqualValues( + t.t, rpcAsset.Amount, + uniFetchResp.IssuanceSubtreeRoot.RootNode.RootSum, + ) + + // Assert that the burn subtree root sum is zero, as no assets have + // been burned. + require.EqualValues( + t.t, 0, + uniFetchResp.BurnSubtreeRoot.RootNode.RootSum, + ) + + // Assert that the ignore subtree root sum equals the total issued + // amount, since the entire issuance has been recorded as ignored. + require.EqualValues( + t.t, rpcAsset.Amount, + uniFetchResp.IgnoreSubtreeRoot.RootNode.RootSum, + ) } // AssertInclusionProof checks that the inclusion proof for a given leaf key From ded200f6dfb600d995c708f0040eda3f1e547a70 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 26 Aug 2025 22:24:41 +0100 Subject: [PATCH 34/51] docs: add release notes --- docs/release-notes/release-notes-0.7.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/release-notes-0.7.0.md b/docs/release-notes/release-notes-0.7.0.md index 0ea50fe0e..ffd16f424 100644 --- a/docs/release-notes/release-notes-0.7.0.md +++ b/docs/release-notes/release-notes-0.7.0.md @@ -58,6 +58,7 @@ - https://github.com/lightninglabs/taproot-assets/pull/1587 - https://github.com/lightninglabs/taproot-assets/pull/1716 - https://github.com/lightninglabs/taproot-assets/pull/1675 + - https://github.com/lightninglabs/taproot-assets/pull/1674 - A new [address version 2 was introduced that supports grouped assets and custom (sender-defined) From 32c401c196ad3d3b9618a10760e17aaae9c5d609 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 29 Aug 2025 10:56:16 +0100 Subject: [PATCH 35/51] supplysync_rpc: add support for FetchSupplyCommit RPC endpoint This enables extending the supplyverifier syncer to support pulling supply commitments in a follow-up commit. --- mssmt/node.go | 15 +++ supplysync_rpc.go | 206 +++++++++++++++++++++++++++++ universe/archive.go | 15 +++ universe/supplycommit/env.go | 14 ++ universe/supplycommit/interface.go | 57 ++++++++ 5 files changed, 307 insertions(+) create mode 100644 universe/supplycommit/interface.go diff --git a/mssmt/node.go b/mssmt/node.go index dbfeff6f0..e29b67f1e 100644 --- a/mssmt/node.go +++ b/mssmt/node.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" + "fmt" ) const ( @@ -23,6 +24,20 @@ var ( // NodeHash represents the key of a MS-SMT node. type NodeHash [hashSize]byte +// NewNodeHashFromBytes creates a new NodeHash from a byte slice. +func NewNodeHashFromBytes(b []byte) (NodeHash, error) { + var zero NodeHash + + if len(b) != hashSize { + return zero, fmt.Errorf("invalid hash size: %d", len(b)) + } + + var h NodeHash + copy(h[:], b) + + return h, nil +} + // String returns a NodeHash as a hex-encoded string. func (k NodeHash) String() string { return hex.EncodeToString(k[:]) diff --git a/supplysync_rpc.go b/supplysync_rpc.go index 36babfbae..f5276ff3c 100644 --- a/supplysync_rpc.go +++ b/supplysync_rpc.go @@ -8,6 +8,8 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/mssmt" + "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/taprpc" unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "github.com/lightninglabs/taproot-assets/universe" @@ -112,6 +114,128 @@ func (r *RpcSupplySync) InsertSupplyCommit(ctx context.Context, return nil } +// FetchSupplyCommit fetches a supply commitment for a specific asset group +// from the remote universe server. +func (r *RpcSupplySync) FetchSupplyCommit(ctx context.Context, + assetSpec asset.Specifier, + spentCommitOutpoint fn.Option[wire.OutPoint]) ( + supplycommit.FetchSupplyCommitResult, error) { + + var zero supplycommit.FetchSupplyCommitResult + + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return zero, fmt.Errorf("unable to unwrap group key: %w", err) + } + + req := &unirpc.FetchSupplyCommitRequest{ + GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{ + GroupKeyBytes: groupKey.SerializeCompressed(), + }, + Locator: &unirpc.FetchSupplyCommitRequest_VeryFirst{ + VeryFirst: true, + }, + } + + // If a spent commit outpoint is provided, use that to locate the next + // supply commitment. + spentCommitOutpoint.WhenSome(func(outpoint wire.OutPoint) { + // nolint: lll + req.Locator = &unirpc.FetchSupplyCommitRequest_SpentCommitOutpoint{ + SpentCommitOutpoint: &taprpc.OutPoint{ + Txid: outpoint.Hash[:], + OutputIndex: outpoint.Index, + }, + } + }) + + resp, err := r.conn.FetchSupplyCommit(ctx, req) + if err != nil { + return zero, fmt.Errorf("unable to fetch supply commitment: %w", + err) + } + + // Unmarshal the chain data to get the root commitment. + rootCommitment, err := unmarshalSupplyCommitChainData(resp.ChainData) + if err != nil { + return zero, fmt.Errorf("unable to unmarshal root "+ + "commitment: %w", err) + } + + // Extract the chain proof from the response data. + chainProof, err := unmarshalChainProof(resp.ChainData) + if err != nil { + return zero, fmt.Errorf("unable to unmarshal chain proof: %w", + err) + } + + // Set the spent commitment outpoint if provided in response. + if resp.SpentCommitmentOutpoint != nil { + spentOutpoint := wire.OutPoint{ + Index: resp.SpentCommitmentOutpoint.OutputIndex, + } + copy(spentOutpoint.Hash[:], resp.SpentCommitmentOutpoint.Txid) + rootCommitment.SpentCommitment = fn.Some(spentOutpoint) + } + + // Unmarshal the supply leaves. + supplyLeaves, err := unmarshalSupplyLeaves( + resp.IssuanceLeaves, resp.BurnLeaves, resp.IgnoreLeaves, + ) + if err != nil { + return zero, fmt.Errorf("unable to unmarshal supply leaves: %w", + err) + } + + // Convert spent commitment outpoint from RPC response to fn.Option. + var respSpentCommitOutpoint fn.Option[wire.OutPoint] + if resp.SpentCommitmentOutpoint != nil { + outpoint := wire.OutPoint{ + Index: resp.SpentCommitmentOutpoint.OutputIndex, + } + copy(outpoint.Hash[:], resp.SpentCommitmentOutpoint.Txid) + respSpentCommitOutpoint = fn.Some(outpoint) + } + + // Unmarshall RPC subtree roots. + issuanceSubtreeRoot, err := unmarshalSupplyCommitSubtreeRoot( + resp.IssuanceSubtreeRoot, + ) + if err != nil { + return zero, fmt.Errorf("unable to unmarshal issuance subtree "+ + "root: %w", err) + } + + burnSubtreeRoot, err := unmarshalSupplyCommitSubtreeRoot( + resp.BurnSubtreeRoot, + ) + if err != nil { + return zero, fmt.Errorf("unable to unmarshal burn subtree "+ + "root: %w", err) + } + + ignoreSubtreeRoot, err := unmarshalSupplyCommitSubtreeRoot( + resp.IgnoreSubtreeRoot, + ) + if err != nil { + return zero, fmt.Errorf("unable to unmarshal ignore subtree "+ + "root: %w", err) + } + + return supplycommit.FetchSupplyCommitResult{ + RootCommitment: *rootCommitment, + SupplyLeaves: *supplyLeaves, + ChainProof: chainProof, + TxChainFeesSats: resp.TxChainFeesSats, + + IssuanceSubtreeRoot: issuanceSubtreeRoot, + BurnSubtreeRoot: burnSubtreeRoot, + IgnoreSubtreeRoot: ignoreSubtreeRoot, + + SpentCommitmentOutpoint: respSpentCommitOutpoint, + }, nil +} + // Close closes the RPC connection to the universe server. func (r *RpcSupplySync) Close() error { if r.conn != nil && r.conn.ClientConn != nil { @@ -174,3 +298,85 @@ func marshalSupplyCommitChainData( return rpcChainData, nil } + +// unmarshalChainProof converts an RPC SupplyCommitChainData into +// a supplycommit.ChainProof. +func unmarshalChainProof( + rpcData *unirpc.SupplyCommitChainData) (supplycommit.ChainProof, + error) { + + var zero supplycommit.ChainProof + + if rpcData == nil { + return zero, fmt.Errorf("supply commit chain data is nil") + } + + var blockHeader wire.BlockHeader + err := blockHeader.Deserialize(bytes.NewReader(rpcData.BlockHeader)) + if err != nil { + return zero, fmt.Errorf("unable to deserialize block "+ + "header: %w", err) + } + + var merkleProof proof.TxMerkleProof + err = merkleProof.Decode(bytes.NewReader(rpcData.TxBlockMerkleProof)) + if err != nil { + return zero, fmt.Errorf("unable to decode merkle proof: %w", + err) + } + + return supplycommit.ChainProof{ + Header: blockHeader, + BlockHeight: rpcData.BlockHeight, + MerkleProof: merkleProof, + TxIndex: rpcData.TxIndex, + }, nil +} + +// unmarshalSupplyCommitSubtreeRoot converts an RPC SubtreeRootProof +// into a domain-specific SubtreeRootProof. +func unmarshalSupplyCommitSubtreeRoot(rpcRoot *unirpc.SupplyCommitSubtreeRoot) ( + supplycommit.SubtreeRootProof, error) { + + var zero supplycommit.SubtreeRootProof + + if rpcRoot == nil { + return zero, nil + } + + // Convert the RPC string type to SupplySubTree enum. + subTreeType, err := supplycommit.NewSubtreeTypeFromStr(rpcRoot.Type) + if err != nil { + return zero, fmt.Errorf("unknown subtree type: %w", err) + } + + // Convert the RPC MerkleSumNode to our domain BranchNode. + if rpcRoot.RootNode == nil { + return zero, fmt.Errorf("supply root node is nil") + } + + // Create a computed branch from the RPC node data. + nodeHash, err := mssmt.NewNodeHashFromBytes(rpcRoot.RootNode.RootHash) + if err != nil { + return zero, fmt.Errorf("unable to parse node hash: %w", err) + } + + rootNode := mssmt.NewComputedBranch( + nodeHash, uint64(rpcRoot.RootNode.RootSum), + ) + + // Convert the byte slice to a UniverseKey array. + leafKey, err := universe.NewUniverseKeyFromBytes( + rpcRoot.SupplyTreeLeafKey, + ) + if err != nil { + return zero, fmt.Errorf("unable to parse leaf key: %w", err) + } + + return supplycommit.SubtreeRootProof{ + Type: subTreeType, + RootNode: *rootNode, + SupplyTreeLeafKey: leafKey, + SupplyTreeInclusionProof: rpcRoot.SupplyTreeInclusionProof, + }, nil +} diff --git a/universe/archive.go b/universe/archive.go index cc7a2b893..2e1835ad0 100644 --- a/universe/archive.go +++ b/universe/archive.go @@ -570,6 +570,21 @@ func (a *Archive) UpsertProofLeafBatch(ctx context.Context, // UniverseKey represents the key used to locate an item within a universe. type UniverseKey [32]byte +// NewUniverseKeyFromBytes creates a new universe key from the given byte slice. +func NewUniverseKeyFromBytes(b []byte) (UniverseKey, error) { + var zero UniverseKey + + if len(b) != 32 { + return zero, fmt.Errorf("invalid length for universe key, "+ + "expected 32 got %d", len(b)) + } + + var key UniverseKey + copy(key[:], b) + + return key, nil +} + // getPrevAssetSnapshot returns the previous asset snapshot for the passed // proof. If the proof is a genesis proof, then nil is returned. func (a *Archive) getPrevAssetSnapshot(ctx context.Context, diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 6e7fdab09..9b31624f6 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -55,6 +55,20 @@ const ( IgnoreTreeType ) +// NewSubtreeTypeFromStr returns the SupplySubTree type from a string. +func NewSubtreeTypeFromStr(s string) (SupplySubTree, error) { + switch s { + case "mint_supply": + return MintTreeType, nil + case "burn": + return BurnTreeType, nil + case "ignore": + return IgnoreTreeType, nil + default: + return 0, fmt.Errorf("unknown supply subtree: %s", s) + } +} + // String returns the string representation of the supply sub tree. func (s SupplySubTree) String() string { switch s { diff --git a/universe/supplycommit/interface.go b/universe/supplycommit/interface.go new file mode 100644 index 000000000..eac01aa14 --- /dev/null +++ b/universe/supplycommit/interface.go @@ -0,0 +1,57 @@ +package supplycommit + +import ( + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/mssmt" + "github.com/lightninglabs/taproot-assets/universe" +) + +// SubtreeRootProof represents the root of a supply commit subtree with its main +// supply tree inclusion proof. +type SubtreeRootProof struct { + // Type indicates the type of the supply commit subtree. + Type SupplySubTree + + // RootNode is the root node of the supply commit subtree. + RootNode mssmt.BranchNode + + // SupplyTreeLeafKey locates the subtree leaf node in the supply commit + // tree. + SupplyTreeLeafKey universe.UniverseKey + + // SupplyTreeInclusionProof proves inclusion of the subtree root in the + // supply tree. + SupplyTreeInclusionProof []byte +} + +// FetchSupplyCommitResult represents the complete data returned from a +// FetchSupplyCommit RPC call, containing all fields from the RPC response. +type FetchSupplyCommitResult struct { + // RootCommitment contains the commitment transaction and output data. + RootCommitment RootCommitment + + // SupplyLeaves contains the issuance, burn, and ignore leaves. + SupplyLeaves SupplyLeaves + + // ChainProof contains the block header and merkle proof. + ChainProof ChainProof + + // TxChainFeesSats is the total number of satoshis in on-chain fees + // paid by the supply commitment transaction. + TxChainFeesSats int64 + + // IssuanceSubtreeRoot is the root of the issuance tree for the asset. + IssuanceSubtreeRoot SubtreeRootProof + + // BurnSubtreeRoot is the root of the burn tree for the asset. + BurnSubtreeRoot SubtreeRootProof + + // IgnoreSubtreeRoot is the root of the ignore tree for the asset. + IgnoreSubtreeRoot SubtreeRootProof + + // SpentCommitmentOutpoint is the outpoint of the previous commitment + // that this new commitment is spending. This is None for the very + // first supply commitment of a grouped asset. + SpentCommitmentOutpoint fn.Option[wire.OutPoint] +} From 94b1ad64ac5a21ef49e00b2dbaf66ddfb8bbfde4 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 29 Aug 2025 11:02:37 +0100 Subject: [PATCH 36/51] supplyverifier: add supply commit pull functionality to syncer Introduce the PullSupplyCommitment method, which calls the FetchSupplyCommit RPC endpoint. Support for calling this endpoint from the syncer was added in the previous commit. --- universe/supplyverifier/syncer.go | 162 +++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-) diff --git a/universe/supplyverifier/syncer.go b/universe/supplyverifier/syncer.go index 1a24ea591..14ad60ae4 100644 --- a/universe/supplyverifier/syncer.go +++ b/universe/supplyverifier/syncer.go @@ -4,13 +4,20 @@ import ( "context" "fmt" "net/url" + "time" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightninglabs/taproot-assets/universe/supplycommit" ) +const ( + // defaultPullTimeout is the default timeout for a supply commitment + defaultPullTimeout = 30 * time.Second +) + // UniverseClient is an interface that represents a client connection to a // remote universe server. type UniverseClient interface { @@ -21,6 +28,12 @@ type UniverseClient interface { updateLeaves supplycommit.SupplyLeaves, chainProof supplycommit.ChainProof) error + // FetchSupplyCommit fetches a supply commitment for a specific + // asset group from the remote universe server. + FetchSupplyCommit(ctx context.Context, assetSpec asset.Specifier, + spentCommitOutpoint fn.Option[wire.OutPoint]) ( + supplycommit.FetchSupplyCommitResult, error) + // Close closes the fetcher and cleans up any resources. Close() error } @@ -214,11 +227,156 @@ func (s *SupplySyncer) PushSupplyCommitment(ctx context.Context, } errorMap := make(map[string]error) - for idx, fetchErr := range pushErrs { + for idx, pushErr := range pushErrs { serverAddr := targetAddrs[idx] hostStr := serverAddr.HostStr() - errorMap[hostStr] = fetchErr + errorMap[hostStr] = pushErr } return errorMap, nil } + +// SupplyCommitPullResult represents the result of a supply commitment pull +// operation across multiple universe servers. +type SupplyCommitPullResult struct { + // FetchResult contains the complete fetched supply commitment data. + FetchResult fn.Option[supplycommit.FetchSupplyCommitResult] + + // ErrorMap contains errors encountered while pulling from each server, + // keyed by server host string. If empty, all pulls succeeded. + ErrorMap map[string]error +} + +// pullUniServer fetches the supply commitment from a specific universe server. +func (s *SupplySyncer) pullUniServer(ctx context.Context, + assetSpec asset.Specifier, spentCommitOutpoint fn.Option[wire.OutPoint], + serverAddr universe.ServerAddr) (supplycommit.FetchSupplyCommitResult, + error) { + + var zero supplycommit.FetchSupplyCommitResult + + // Create a client for the specific universe server address. + client, err := s.cfg.ClientFactory(serverAddr) + if err != nil { + return zero, fmt.Errorf("unable to create universe client: %w", + err) + } + + // Ensure the client is properly closed when we're done. + defer func() { + if closeErr := client.Close(); closeErr != nil { + log.Errorf("Unable to close supply syncer pull "+ + "universe client: %v", closeErr) + } + }() + + result, err := client.FetchSupplyCommit( + ctx, assetSpec, spentCommitOutpoint, + ) + if err != nil { + return zero, fmt.Errorf("unable to fetch supply commitment: %w", + err) + } + + return result, nil +} + +// PullSupplyCommitment fetches a supply commitment from remote universe +// servers. This function attempts to fetch from all servers in parallel. +// +// Returns a SupplyCommitPullResult containing the fetched data and a map of +// per-server errors, plus an internal error. If at least one server succeeds, +// the result will contain the commitment data. If all servers fail, the +// ErrorMap will contain all the errors and the commitment data will be nil. +// +// NOTE: This function must be thread safe. +func (s *SupplySyncer) PullSupplyCommitment(ctx context.Context, + assetSpec asset.Specifier, spentCommitOutpoint fn.Option[wire.OutPoint], + canonicalUniverses []url.URL) (SupplyCommitPullResult, error) { + + var zero SupplyCommitPullResult + + targetAddrs, err := s.fetchServerAddrs(ctx, canonicalUniverses) + if err != nil { + // This is an internal error that prevents the operation from + // proceeding. + return zero, fmt.Errorf("unable to fetch target universe "+ + "server addresses: %w", err) + } + + // Pull the supply commitment from all target universe servers in + // parallel. Store both errors and successful results. + results := make(map[string]supplycommit.FetchSupplyCommitResult) + + // Specify context timeout for the entire pull operation. + ctxPull, cancel := context.WithTimeout(ctx, defaultPullTimeout) + defer cancel() + + pullErrs, err := fn.ParSliceErrCollect( + ctxPull, targetAddrs, func(ctx context.Context, + serverAddr universe.ServerAddr) error { + + // Pull the supply commitment from the universe server. + result, err := s.pullUniServer( + ctx, assetSpec, spentCommitOutpoint, serverAddr, + ) + if err != nil { + return fmt.Errorf("unable to pull supply "+ + "commitment (server_addr_id=%d, "+ + "server_addr_host_str=%s): %w", + serverAddr.ID, serverAddr.HostStr(), + err) + } + + results[serverAddr.HostStr()] = result + return nil + }, + ) + if err != nil { + // This should not happen with ParSliceErrCollect, but handle it + // as an internal error. + return zero, fmt.Errorf("unable to pull supply commitment: %w", + err) + } + + // Report results: log server address and supply tree root. + // + // If the supply commitment that was pulled fails verification later, + // we can use this log to trace back to the server it came from. + for serverAddr, res := range results { + // Format the spent outpoint if present, otherwise empty string. + spentOutpointStr := fn.MapOptionZ( + spentCommitOutpoint, func(op wire.OutPoint) string { + return op.String() + }, + ) + + log.Infof("Pulled supply commitment from server "+ + "(server_addr=%s, asset=%s, supply_tree_root=%s, "+ + "spent_outpoint=%s)", serverAddr, assetSpec.String(), + res.RootCommitment.SupplyRoot.NodeHash().String(), + spentOutpointStr) + } + + // Return one successful result, if available, as the final outcome. + var finalResult *supplycommit.FetchSupplyCommitResult + for _, res := range results { + finalResult = &res + break + } + + // Build a map from server addresses to their corresponding errors. + errorMap := make(map[string]error) + for idx, pullErr := range pullErrs { + serverAddr := targetAddrs[idx] + hostStr := serverAddr.HostStr() + if pullErr != nil { + errorMap[hostStr] = pullErr + } + } + + return SupplyCommitPullResult{ + FetchResult: fn.MaybeSome(finalResult), + ErrorMap: errorMap, + }, nil +} From 258c734011d10ec1a850fee1040df9a38aed0aed Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 29 Aug 2025 13:15:42 +0100 Subject: [PATCH 37/51] supplycommit: refactor by introducing startAssetSM method Consolidate all state machine startup logic into a new method called startAssetSM to improve code clarity. Added IsRunning checks to ensure the state machine is active before returning it from the cache. --- universe/supplycommit/manager.go | 104 +++++++++++++++++++------------ 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index c9aa96195..7f9869c7c 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -191,46 +191,11 @@ func (m *Manager) ensureSupplyCommitSupport(ctx context.Context, return nil } -// fetchStateMachine retrieves a state machine from the cache or creates a -// new one if it doesn't exist. If a new state machine is created, it is also -// started. -func (m *Manager) fetchStateMachine( +// startAssetSM creates and starts a new supply commitment state +// machine for the given asset specifier. +func (m *Manager) startAssetSM(ctx context.Context, assetSpec asset.Specifier) (*StateMachine, error) { - groupKey, err := assetSpec.UnwrapGroupKeyOrErr() - if err != nil { - return nil, fmt.Errorf("asset specifier missing group key: %w", - err) - } - - // Check if the state machine for the asset group already exists in the - // cache. - sm, ok := m.smCache.Get(*groupKey) - if ok { - return sm, nil - } - - // If the state machine is not found, create a new one. - // - // Before we can create a state machine, we need to ensure that the - // asset group supports supply commitments. If it doesn't, then we - // return an error. - ctx, cancel := m.WithCtxQuitNoTimeout() - defer cancel() - - metaReveal, err := fetchLatestAssetMetadata( - ctx, m.cfg.AssetLookup, assetSpec, - ) - if err != nil { - return nil, fmt.Errorf("faild to fetch asset meta: %w", err) - } - - err = m.ensureSupplyCommitSupport(ctx, metaReveal) - if err != nil { - return nil, fmt.Errorf("failed to ensure supply commit "+ - "support for asset: %w", err) - } - env := &Environment{ AssetSpec: assetSpec, TreeView: m.cfg.TreeView, @@ -270,6 +235,12 @@ func (m *Manager) fetchStateMachine( smCtx, _ := m.WithCtxQuitNoTimeout() newSm.Start(smCtx) + // Assert that the state machine is running. Start should block until + // the state machine is running. + if !newSm.IsRunning() { + return nil, fmt.Errorf("state machine unexpectadly not running") + } + // If specific initial states are provided, we send the corresponding // events to the state machine to ensure it begins ticking as expected. switch initialState.(type) { @@ -287,11 +258,64 @@ func (m *Manager) fetchStateMachine( newSm.SendEvent(ctx, &FinalizeEvent{}) } - m.smCache.Set(*groupKey, &newSm) - return &newSm, nil } +// fetchStateMachine retrieves a state machine from the cache or creates a +// new one if it doesn't exist. If a new state machine is created, it is also +// started. +func (m *Manager) fetchStateMachine( + assetSpec asset.Specifier) (*StateMachine, error) { + + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return nil, fmt.Errorf("asset specifier missing group key: %w", + err) + } + + // Check if the state machine for the asset group already exists in the + // cache. + sm, ok := m.smCache.Get(*groupKey) + if ok { + // If the state machine is found and is running, return it. + if sm.IsRunning() { + return sm, nil + } + + // If the state machine exists but is not running, replace it in + // the cache with a new running instance. + } + + // Before we can create a state machine, we need to ensure that the + // asset group supports supply commitments. If it doesn't, then we + // return an error. + ctx, cancel := m.WithCtxQuitNoTimeout() + defer cancel() + + metaReveal, err := fetchLatestAssetMetadata( + ctx, m.cfg.AssetLookup, assetSpec, + ) + if err != nil { + return nil, fmt.Errorf("faild to fetch asset meta: %w", err) + } + + err = m.ensureSupplyCommitSupport(ctx, metaReveal) + if err != nil { + return nil, fmt.Errorf("failed to ensure supply commit "+ + "support for asset: %w", err) + } + + // Start the state machine and add it to the cache. + newSm, err := m.startAssetSM(ctx, assetSpec) + if err != nil { + return nil, fmt.Errorf("unable to start state machine: %w", err) + } + + m.smCache.Set(*groupKey, newSm) + + return newSm, nil +} + // SendEvent sends an event to the state machine associated with the given asset // specifier. If a state machine for the asset group does not exist, it will be // created and started. From f4ea8f0f241430e4c17fda6ed163d144c705deeb Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 29 Aug 2025 15:08:48 +0100 Subject: [PATCH 38/51] supplyverifier: refactor by introducing startAssetSM method Consolidate all state machine startup logic into a new method called startAssetSM to improve code clarity. Added IsRunning checks to ensure the state machine is active before returning it from the cache. --- universe/supplyverifier/manager.go | 77 +++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index 339045578..286cba0ea 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -146,24 +146,10 @@ func (m *Manager) Stop() error { return nil } -// fetchStateMachine retrieves a state machine from the cache or creates a -// new one if it doesn't exist. If a new state machine is created, it is also -// started. -func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, - error) { - - groupKey, err := assetSpec.UnwrapGroupKeyOrErr() - if err != nil { - return nil, fmt.Errorf("asset specifier missing group key: %w", - err) - } - - // Check if the state machine for the asset group already exists in the - // cache. - sm, ok := m.smCache.Get(*groupKey) - if ok { - return sm, nil - } +// startAssetSM creates and starts a new supply commitment state machine for the +// given asset specifier. +func (m *Manager) startAssetSM(ctx context.Context, + assetSpec asset.Specifier) (*StateMachine, error) { // If the state machine is not found, create a new one. env := &Environment{ @@ -176,9 +162,6 @@ func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, // Before we start the state machine, we'll need to fetch the current // state from disk, to see if we need to emit any new events. - ctx, cancel := m.WithCtxQuitNoTimeout() - defer cancel() - initialState, err := m.cfg.StateLog.FetchState(ctx, assetSpec) if err != nil { return nil, fmt.Errorf("unable to fetch current state: %w", err) @@ -201,15 +184,63 @@ func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, smCtx, _ := m.WithCtxQuitNoTimeout() newSm.Start(smCtx) + // Assert that the state machine is running. Start should block until + // the state machine is running. + if !newSm.IsRunning() { + return nil, fmt.Errorf("state machine unexpectadly not running") + } + // For supply verifier, we always start with an InitEvent to begin // the verification process. newSm.SendEvent(ctx, &InitEvent{}) - m.smCache.Set(*groupKey, &newSm) - return &newSm, nil } +// fetchStateMachine retrieves a state machine from the cache or creates a +// new one if it doesn't exist. If a new state machine is created, it is also +// started. +func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, + error) { + + groupKey, err := assetSpec.UnwrapGroupKeyOrErr() + if err != nil { + return nil, fmt.Errorf("asset specifier missing group key: %w", + err) + } + + // Check if the state machine for the asset group already exists in the + // cache. + sm, ok := m.smCache.Get(*groupKey) + if ok { + // If the state machine is found and is running, return it. + if sm.IsRunning() { + return sm, nil + } + + // If the state machine exists but is not running, replace it in + // the cache with a new running instance. + } + + ctx, cancel := m.WithCtxQuitNoTimeout() + defer cancel() + + // TODO(ffranr): Check that the asset group supports supply commitments + // and that this node does not create supply commitments for the asset + // group (i.e. it does not own the delegation key). We don't want to + // run a verifier state machine for an asset group supply commitment + // that we issue ourselves. + + newSm, err := m.startAssetSM(ctx, assetSpec) + if err != nil { + return nil, fmt.Errorf("unable to start state machine: %w", err) + } + + m.smCache.Set(*groupKey, newSm) + + return newSm, nil +} + // InsertSupplyCommit stores a verified supply commitment for the given asset // group in the node's local database. func (m *Manager) InsertSupplyCommit(ctx context.Context, From 5d936e9cd00c07fdedc2453450b001b8ce35e39b Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 29 Aug 2025 16:37:56 +0100 Subject: [PATCH 39/51] tapdb: add TapAddressBook.FetchSupplyCommitAssets Add a method that returns all asset groups with supply commitment enabled. --- tapdb/addrs.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tapdb/addrs.go b/tapdb/addrs.go index dae73d521..bf283d7da 100644 --- a/tapdb/addrs.go +++ b/tapdb/addrs.go @@ -233,6 +233,9 @@ type AddrBook interface { // database. FetchAllAssetMeta(ctx context.Context) ([]AllAssetMetaRow, error) + // FetchGroupedAssets fetches all assets with non-nil group keys. + FetchGroupedAssets(ctx context.Context) ([]RawGroupedAsset, error) + // QueryLastEventHeight queries the last event height for a given // address version. QueryLastEventHeight(ctx context.Context, @@ -1637,6 +1640,102 @@ func (t *TapAddressBook) FetchInternalKeyLocator(ctx context.Context, return keyLoc, nil } +// FetchSupplyCommitAssets fetches all assets with non-nil group keys that could +// potentially be involved in supply commitments. +func (t *TapAddressBook) FetchSupplyCommitAssets(ctx context.Context, + localControlled bool) ([]btcec.PublicKey, error) { + + var ( + readOpts = NewAddrBookReadTx() + assetGroupKeys []btcec.PublicKey + ) + + err := t.db.ExecTx(ctx, &readOpts, func(db AddrBook) error { + // Fetch all grouped assets from the database. + dbAssets, err := db.FetchGroupedAssets(ctx) + if err != nil { + return err + } + + // Convert to our simplified format. + assetGroupKeys = make([]btcec.PublicKey, 0, len(dbAssets)) + for idx := range dbAssets { + dbAsset := dbAssets[idx] + + groupKey, err := btcec.ParsePubKey( + dbAsset.TweakedGroupKey, + ) + if err != nil { + return fmt.Errorf("unable to parse group "+ + "key: %w", err) + } + + // Get asset metadata for this group to check if it + // supports supply commitments. + metaRow, err := db.FetchAssetMetaForAsset( + ctx, dbAsset.AssetID, + ) + if err != nil { + // If metadata not found, skip this asset group + // as it doesn't support supply commitments. + continue + } + + // Check if the asset group supports supply commitments. + assetMetaRow := metaRow.AssetsMetum + if !assetMetaRow.MetaUniverseCommitments.Valid || + !assetMetaRow.MetaUniverseCommitments.Bool { + + continue + } + + // Check if a delegation key is present (required for + // supply commitments). + if len(metaRow.AssetsMetum.MetaDelegationKey) == 0 { + continue + } + + // Parse delegation key from metadata. + delegationPubKey, err := btcec.ParsePubKey( + metaRow.AssetsMetum.MetaDelegationKey, + ) + if err != nil { + continue + } + + // Filter based on localControlled parameter: + // - If localControlled=true: only return assets where + // we own the delegation key + // - If localControlled=false: only return assets where + // we DON'T own the delegation key + _, err = db.FetchInternalKeyLocator( + ctx, delegationPubKey.SerializeCompressed(), + ) + weOwnDelegationKey := err == nil + + if localControlled && !weOwnDelegationKey { + // We want assets under local control, but we do + // not own this one. + continue + } + if !localControlled && weOwnDelegationKey { + // We want assets not under local control, but + // we own this one. + continue + } + + assetGroupKeys = append(assetGroupKeys, *groupKey) + } + + return nil + }) + if err != nil { + return nil, err + } + + return assetGroupKeys, nil +} + // A set of compile-time assertions to ensure that TapAddressBook meets the // address.Storage and address.EventStorage interface. var _ address.Storage = (*TapAddressBook)(nil) From 59f9b071570f7be1c93979756da3655763bee1ae Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 1 Sep 2025 11:34:51 +0100 Subject: [PATCH 40/51] tapdb: add SQL query QueryLatestSupplyCommitment Add a new SQL query to fetch the latest stored supply commitment based on the highest block height. --- tapdb/sqlc/querier.go | 1 + tapdb/sqlc/queries/supply_commit.sql | 9 +++++++ tapdb/sqlc/supply_commit.sql.go | 36 ++++++++++++++++++++++++++++ tapdb/supply_commit.go | 5 ++++ 4 files changed, 51 insertions(+) diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index c9cc85d3b..f7c3ce19a 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -184,6 +184,7 @@ type Querier interface { QueryFederationProofSyncLog(ctx context.Context, arg QueryFederationProofSyncLogParams) ([]QueryFederationProofSyncLogRow, error) QueryFederationUniSyncConfigs(ctx context.Context) ([]QueryFederationUniSyncConfigsRow, error) QueryLastEventHeight(ctx context.Context, version int16) (int64, error) + QueryLatestSupplyCommitment(ctx context.Context, groupKey []byte) (QueryLatestSupplyCommitmentRow, error) QueryMultiverseLeaves(ctx context.Context, arg QueryMultiverseLeavesParams) ([]QueryMultiverseLeavesRow, error) QueryPassiveAssets(ctx context.Context, transferID int64) ([]QueryPassiveAssetsRow, error) QueryPendingSupplyCommitTransition(ctx context.Context, groupKey []byte) (QueryPendingSupplyCommitTransitionRow, error) diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 8cccddc73..6f0cfa899 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -173,6 +173,15 @@ FROM supply_commitments AS sc WHERE sc.spent_commitment IS NULL AND sc.group_key = @group_key; +-- name: QueryLatestSupplyCommitment :one +SELECT sqlc.embed(sc), ct.tx_index +FROM supply_commitments AS sc +JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.group_key = @group_key +ORDER BY ct.block_height DESC + LIMIT 1; + -- name: QuerySupplyCommitmentOutpoint :one SELECT ct.txid, sc.output_index FROM supply_commitments AS sc diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index d33ae0104..a72fae381 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -394,6 +394,42 @@ func (q *Queries) QueryExistingPendingTransition(ctx context.Context, groupKey [ return transition_id, err } +const QueryLatestSupplyCommitment = `-- name: QueryLatestSupplyCommitment :one +SELECT sc.commit_id, sc.group_key, sc.chain_txn_id, sc.output_index, sc.internal_key_id, sc.output_key, sc.block_header, sc.block_height, sc.merkle_proof, sc.supply_root_hash, sc.supply_root_sum, sc.spent_commitment, ct.tx_index +FROM supply_commitments AS sc +JOIN chain_txns AS ct + ON sc.chain_txn_id = ct.txn_id +WHERE sc.group_key = $1 +ORDER BY ct.block_height DESC + LIMIT 1 +` + +type QueryLatestSupplyCommitmentRow struct { + SupplyCommitment SupplyCommitment + TxIndex sql.NullInt32 +} + +func (q *Queries) QueryLatestSupplyCommitment(ctx context.Context, groupKey []byte) (QueryLatestSupplyCommitmentRow, error) { + row := q.db.QueryRowContext(ctx, QueryLatestSupplyCommitment, groupKey) + var i QueryLatestSupplyCommitmentRow + err := row.Scan( + &i.SupplyCommitment.CommitID, + &i.SupplyCommitment.GroupKey, + &i.SupplyCommitment.ChainTxnID, + &i.SupplyCommitment.OutputIndex, + &i.SupplyCommitment.InternalKeyID, + &i.SupplyCommitment.OutputKey, + &i.SupplyCommitment.BlockHeader, + &i.SupplyCommitment.BlockHeight, + &i.SupplyCommitment.MerkleProof, + &i.SupplyCommitment.SupplyRootHash, + &i.SupplyCommitment.SupplyRootSum, + &i.SupplyCommitment.SpentCommitment, + &i.TxIndex, + ) + return i, err +} + const QueryPendingSupplyCommitTransition = `-- name: QueryPendingSupplyCommitTransition :one WITH target_machine AS ( SELECT group_key diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 033bb8788..92c4c09b7 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -190,6 +190,11 @@ type SupplyCommitStore interface { QueryStartingSupplyCommitment(ctx context.Context, groupKey []byte) (sqlc.QueryStartingSupplyCommitmentRow, error) + // QueryLatestSupplyCommitment fetches the latest supply commitment + // of an asset group based on highest block height. + QueryLatestSupplyCommitment(ctx context.Context, + groupKey []byte) (sqlc.QueryLatestSupplyCommitmentRow, error) + // QuerySupplyCommitmentOutpoint fetches the outpoint of a supply // commitment by its ID. QuerySupplyCommitmentOutpoint(ctx context.Context, From b88b5744a3ac374f55f1a7d6a4233cc96d09f4b9 Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 1 Sep 2025 11:35:40 +0100 Subject: [PATCH 41/51] tapdb: add method FetchLatestCommitment to SupplyCommitMachine db store --- tapdb/supply_commit.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 92c4c09b7..e41d4aad4 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -1327,6 +1327,56 @@ func (s *SupplyCommitMachine) FetchStartingCommitment(ctx context.Context, return commit, nil } +// FetchLatestCommitment fetches the latest supply commitment of an asset +// group based on highest block height. If no commitment is found, it returns +// ErrCommitmentNotFound. +func (s *SupplyCommitMachine) FetchLatestCommitment(ctx context.Context, + assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) { + + groupKey := assetSpec.UnwrapGroupKeyToPtr() + if groupKey == nil { + return nil, ErrMissingGroupKey + } + + var ( + writeTx = WriteTxOption() + groupKeyBytes = groupKey.SerializeCompressed() + commit *supplycommit.RootCommitment + ) + dbErr := s.db.ExecTx(ctx, writeTx, func(db SupplyCommitStore) error { + // First, fetch the supply commitment by group key. + commitRow, err := db.QueryLatestSupplyCommitment( + ctx, groupKeyBytes, + ) + if err != nil { + return fmt.Errorf("failed to query latest "+ + "commitment for group %x: %w", groupKeyBytes, + err) + } + + commit, err = parseSupplyCommitmentRow( + ctx, commitRow.SupplyCommitment, commitRow.TxIndex, db, + ) + if err != nil { + return fmt.Errorf("failed to parse latest "+ + "commitment for group %x: %w", groupKeyBytes, + err) + } + + return nil + }) + if dbErr != nil { + if errors.Is(dbErr, sql.ErrNoRows) { + return nil, supplyverifier.ErrCommitmentNotFound + } + + return nil, fmt.Errorf("failed to fetch latest commitment "+ + "for group %x: %w", groupKeyBytes, dbErr) + } + + return commit, nil +} + // parseSupplyCommitmentRow parses a SupplyCommitment row into a // supplycommit.RootCommitment and optional commitmentChainInfo. func parseSupplyCommitmentRow(ctx context.Context, commit SupplyCommitment, From f612b0023e75809a9db463dd64786e9bbd4be9ab Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 29 Aug 2025 17:17:10 +0100 Subject: [PATCH 42/51] supplyverifier: init state machines for asset groups with commitments Query the local database for asset groups with supply commitment enabled where the node cannot publish commitments (no delegation key). These are commitments published by external issuers. Commitments from the current node are excluded, as they do not need to be tracked by the verifier. A temporary limit of 50 state machines is set to prevent tapd nodes or universe servers from exhausting resources. --- tapcfg/server.go | 1 + universe/supplycommit/env.go | 5 ++ universe/supplycommit/mock.go | 10 ++++ universe/supplyverifier/manager.go | 90 ++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/tapcfg/server.go b/tapcfg/server.go index 395eb98f3..bc2a3b067 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -554,6 +554,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, Chain: chainBridge, SupplyCommitView: supplyCommitStore, SupplyTreeView: supplyTreeStore, + AssetLookup: tapdbAddrBook, IssuanceSubscriptions: universeSyncer, DaemonAdapters: lndFsmDaemonAdapters, }, diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 9b31624f6..c71d4c8ee 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -214,6 +214,11 @@ func NewSupplyLeavesFromEvents(events []SupplyUpdateEvent) (SupplyLeaves, // AssetLookup is an interface that allows us to query for asset // information, such as asset groups and asset metadata. type AssetLookup interface { + // FetchSupplyCommitAssets fetches all assets with non-nil group keys + // that are supply commitments enabled. + FetchSupplyCommitAssets(ctx context.Context, + localControlled bool) ([]btcec.PublicKey, error) + // QueryAssetGroupByGroupKey fetches the asset group with a matching // tweaked key, including the genesis information used to create the // group. diff --git a/universe/supplycommit/mock.go b/universe/supplycommit/mock.go index 13c9f2e2a..b1c37c6e8 100644 --- a/universe/supplycommit/mock.go +++ b/universe/supplycommit/mock.go @@ -436,6 +436,16 @@ type mockAssetLookup struct { mock.Mock } +func (m *mockAssetLookup) FetchSupplyCommitAssets(ctx context.Context, + localControlled bool) ([]btcec.PublicKey, error) { + + args := m.Called(ctx, localControlled) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]btcec.PublicKey), args.Error(1) +} + func (m *mockAssetLookup) QueryAssetGroupByGroupKey(ctx context.Context, groupKey *btcec.PublicKey) (*asset.AssetGroup, error) { diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index 286cba0ea..7a07f1bc7 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -20,6 +20,10 @@ import ( const ( // DefaultTimeout is the context guard default timeout. DefaultTimeout = 30 * time.Second + + // MaxStateMachines is the maximum number of supply verifier state + // machines that can be managed by the multi state machine manager. + MaxStateMachines = 50 ) // DaemonAdapters is a wrapper around the protofsm.DaemonAdapters interface @@ -70,6 +74,10 @@ type ManagerCfg struct { // SupplyTreeView is used to fetch supply leaves by height. SupplyTreeView SupplyTreeView + // AssetLookup is used to look up asset information such as asset groups + // and asset metadata. + AssetLookup supplycommit.AssetLookup + // SupplySyncer is used to retrieve supply leaves from a universe and // persist them to the local database. SupplySyncer SupplySyncer @@ -121,13 +129,87 @@ func NewManager(cfg ManagerCfg) *Manager { } } +// InitStateMachines initializes state machines for all asset groups that +// support supply commitments. If a state machine for an asset group already +// exists, it will be skipped. +func (m *Manager) InitStateMachines() error { + ctx, cancel := m.WithCtxQuitNoTimeout() + defer cancel() + + // First, get all assets with group keys that could potentially be + // involved in supply commitments. The Manager will filter these + // based on delegation key ownership and other criteria. + assetGroupKeys, err := m.cfg.AssetLookup.FetchSupplyCommitAssets( + ctx, false, + ) + if err != nil { + return fmt.Errorf("unable to fetch supply commit assets: %w", + err) + } + + for idx := range assetGroupKeys { + groupKey := assetGroupKeys[idx] + + // Create asset specifier from group key. + assetSpec := asset.NewSpecifierFromGroupKey(groupKey) + + // Check to ensure state machine for asset group does not + // already exist. + _, ok := m.smCache.Get(groupKey) + if ok { + continue + } + + // Safeguard against universe server misconfiguration or + // otherwise ending up with too many state machines. + // + // TODO(ffranr): Add config option instead of hard limit. + if m.smCache.Count() >= MaxStateMachines { + log.Infof("Maximum number of state machines (%d) "+ + "reached, skipping initialization of new "+ + "state machine", MaxStateMachines) + break + } + + // Create and start a new state machine for the asset group. + newSm, err := m.startAssetSM(ctx, assetSpec) + if err != nil { + return fmt.Errorf("unable to start state machine for "+ + "asset group (asset=%s): %w", + assetSpec.String(), err) + } + + m.smCache.Set(groupKey, newSm) + } + + return nil +} + // Start starts the multi state machine manager. func (m *Manager) Start() error { + var startErr error + m.startOnce.Do(func() { // Initialize the state machine cache. m.smCache = newStateMachineCache() + + // Initialize state machines for all relevant asset groups. + // + // TODO(ffranr): Consider passing in tapd operation mode + // (e.g. universe server, etc) to determine which asset groups + // we should run state machines for. + err := m.InitStateMachines() + if err != nil { + startErr = fmt.Errorf("unable to initialize state "+ + "machines: %v", err) + return + } }) + if startErr != nil { + return fmt.Errorf("unable to start manager: %w", startErr) + } + return nil } @@ -631,6 +713,14 @@ func (c *stateMachineCache) StopAll() { } } +// Count returns the number of state machines in the cache. +func (c *stateMachineCache) Count() int { + c.mu.RLock() + defer c.mu.RUnlock() + + return len(c.cache) +} + // Get retrieves a state machine from the cache. func (c *stateMachineCache) Get(groupPubKey btcec.PublicKey) (*StateMachine, bool) { From a5ac1c3e959bceb2549cfe0ab6b8d8b16e96d21e Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 1 Sep 2025 16:13:03 +0100 Subject: [PATCH 43/51] supplycommit: refactor asset check for reuse in supplyverifier package Extract CheckSupplyCommitSupport function for use in the supplyverifier package. Rename fetchLatestAssetMetadata to FetchLatestAssetMetadata to allow external usage. --- universe/supplycommit/env.go | 68 +++++++++++++++++++++++++++- universe/supplycommit/manager.go | 61 +------------------------ universe/supplycommit/transitions.go | 2 +- 3 files changed, 68 insertions(+), 63 deletions(-) diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index c71d4c8ee..c6fe55fc5 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "errors" "fmt" "net/url" @@ -15,6 +16,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" + "github.com/lightninglabs/taproot-assets/address" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/mssmt" @@ -237,9 +239,9 @@ type AssetLookup interface { rawKey *btcec.PublicKey) (keychain.KeyLocator, error) } -// fetchLatestAssetMetadata returns the latest asset metadata for the +// FetchLatestAssetMetadata returns the latest asset metadata for the // given asset specifier. -func fetchLatestAssetMetadata(ctx context.Context, lookup AssetLookup, +func FetchLatestAssetMetadata(ctx context.Context, lookup AssetLookup, assetSpec asset.Specifier) (proof.MetaReveal, error) { var zero proof.MetaReveal @@ -271,6 +273,68 @@ func fetchLatestAssetMetadata(ctx context.Context, lookup AssetLookup, return *metaReveal, nil } +// CheckSupplyCommitSupport verifies that the asset group for the given +// asset specifier supports supply commitments, and that this node can generate +// supply commitments for it. +func CheckSupplyCommitSupport(ctx context.Context, assetLookup AssetLookup, + assetSpec asset.Specifier, locallyControlled bool) error { + + // Fetch the latest asset metadata for the asset group. + metaReveal, err := FetchLatestAssetMetadata( + ctx, assetLookup, assetSpec, + ) + if err != nil { + return fmt.Errorf("faild to fetch asset meta: %w", err) + } + + // If the universe commitment flag is not set on the asset metadata, + // then the asset group does not support supply commitments. + if !metaReveal.UniverseCommitments { + return fmt.Errorf("asset group metadata universe " + + "commitments flag indicates that asset does not " + + "support supply commitments") + } + + // If a delegation key is not present, then the asset group does not + // support supply commitments. + if metaReveal.DelegationKey.IsNone() { + return fmt.Errorf("asset group metadata does not " + + "specify delegation key, which is required for " + + "supply commitments") + } + + // Extract supply commitment delegation pub key from the asset metadata. + delegationPubKey, err := metaReveal.DelegationKey.UnwrapOrErr( + fmt.Errorf("delegation key not found for given asset"), + ) + if err != nil { + return err + } + + // Fetch the delegation key locator. We need to ensure that the + // delegation key is owned by this node, so that we can generate + // supply commitments (ignore tuples) for this asset group. + _, err = assetLookup.FetchInternalKeyLocator( + ctx, &delegationPubKey, + ) + switch { + case errors.Is(err, address.ErrInternalKeyNotFound): + // If local key control is expected, then we return an error + // if the delegation key locator is not found. + if locallyControlled { + return fmt.Errorf("delegation key locator not found; " + + "only delegation key owners can generate " + + "supply commitments") + } + + case err != nil: + return fmt.Errorf("failed to fetch delegation key locator: %w", + err) + } + + return nil +} + // SupplyTreeView is an interface that allows the state machine to obtain an up // to date snapshot of the root supply tree, as the sub trees (ignore, burn, // mint) committed in the main supply tree. diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go index 7f9869c7c..947d96de7 100644 --- a/universe/supplycommit/manager.go +++ b/universe/supplycommit/manager.go @@ -2,18 +2,15 @@ package supplycommit import ( "context" - "errors" "fmt" "sync" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" - "github.com/lightninglabs/taproot-assets/address" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/mssmt" - "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightningnetwork/lnd/msgmux" @@ -142,55 +139,6 @@ func (m *Manager) Stop() error { return nil } -// ensureSupplyCommitSupport verifies that the asset group for the given -// asset specifier supports supply commitments, and that this node can generate -// supply commitments for it. -func (m *Manager) ensureSupplyCommitSupport(ctx context.Context, - metaReveal proof.MetaReveal) error { - - // If the universe commitment flag is not set on the asset metadata, - // then the asset group does not support supply commitments. - if !metaReveal.UniverseCommitments { - return fmt.Errorf("asset group metadata universe " + - "commitments flag indicates that asset does not " + - "support supply commitments") - } - - // If a delegation key is not present, then the asset group does not - // support supply commitments. - if metaReveal.DelegationKey.IsNone() { - return fmt.Errorf("asset group metadata does not " + - "specify delegation key, which is required for " + - "supply commitments") - } - - // Extract supply commitment delegation pub key from the asset metadata. - delegationPubKey, err := metaReveal.DelegationKey.UnwrapOrErr( - fmt.Errorf("delegation key not found for given asset"), - ) - if err != nil { - return err - } - - // Fetch the delegation key locator. We need to ensure that the - // delegation key is owned by this node, so that we can generate - // supply commitments (ignore tuples) for this asset group. - _, err = m.cfg.AssetLookup.FetchInternalKeyLocator( - ctx, &delegationPubKey, - ) - switch { - case errors.Is(err, address.ErrInternalKeyNotFound): - return fmt.Errorf("delegation key locator not found; " + - "only delegation key owners can ignore asset " + - "outpoints for this asset group") - case err != nil: - return fmt.Errorf("failed to fetch delegation key locator: %w", - err) - } - - return nil -} - // startAssetSM creates and starts a new supply commitment state // machine for the given asset specifier. func (m *Manager) startAssetSM(ctx context.Context, @@ -292,14 +240,7 @@ func (m *Manager) fetchStateMachine( ctx, cancel := m.WithCtxQuitNoTimeout() defer cancel() - metaReveal, err := fetchLatestAssetMetadata( - ctx, m.cfg.AssetLookup, assetSpec, - ) - if err != nil { - return nil, fmt.Errorf("faild to fetch asset meta: %w", err) - } - - err = m.ensureSupplyCommitSupport(ctx, metaReveal) + err = CheckSupplyCommitSupport(ctx, m.cfg.AssetLookup, assetSpec, true) if err != nil { return nil, fmt.Errorf("failed to ensure supply commit "+ "support for asset: %w", err) diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index 4f1e993c1..c9b6684f9 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -1109,7 +1109,7 @@ func (c *CommitFinalizeState) ProcessEvent(event Event, // Retrieve latest canonical universe list from the latest // metadata for the asset group. - metadata, err := fetchLatestAssetMetadata( + metadata, err := FetchLatestAssetMetadata( ctx, env.AssetLookup, env.AssetSpec, ) if err != nil { From 17365f61a836089b03777856160b85d692aa179c Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 1 Sep 2025 17:12:58 +0100 Subject: [PATCH 44/51] supplyverifier: validate asset group before starting state machine Verify that the asset group is supported before starting a supplyverifier state machine for it. --- universe/supplyverifier/manager.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index 7a07f1bc7..a4f31023b 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -307,11 +307,18 @@ func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine, ctx, cancel := m.WithCtxQuitNoTimeout() defer cancel() - // TODO(ffranr): Check that the asset group supports supply commitments - // and that this node does not create supply commitments for the asset - // group (i.e. it does not own the delegation key). We don't want to - // run a verifier state machine for an asset group supply commitment - // that we issue ourselves. + // Check that the asset group supports supply commitments and that + // this node does not create supply commitments for the asset group + // (i.e. it does not own the delegation key). We don't want to run + // a verifier state machine for an asset group supply commitment + // that we issue ourselves. + err = supplycommit.CheckSupplyCommitSupport( + ctx, m.cfg.AssetLookup, assetSpec, false, + ) + if err != nil { + return nil, fmt.Errorf("asset group is not suitable for "+ + "supply verifier state machine: %w", err) + } newSm, err := m.startAssetSM(ctx, assetSpec) if err != nil { From c857617eefce3c03bbd7fafe6bc18c3eafa97673 Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 1 Sep 2025 18:02:27 +0100 Subject: [PATCH 45/51] WIP: supplyverifier: enhance supply verifier state machine --- universe/supplyverifier/env.go | 32 ++- universe/supplyverifier/events.go | 78 ++++++ universe/supplyverifier/manager.go | 2 + universe/supplyverifier/states.go | 74 ++++-- universe/supplyverifier/transitions.go | 329 +++++++++++++++++++++++++ 5 files changed, 484 insertions(+), 31 deletions(-) create mode 100644 universe/supplyverifier/events.go create mode 100644 universe/supplyverifier/transitions.go diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go index 013ec3400..1c9ed8327 100644 --- a/universe/supplyverifier/env.go +++ b/universe/supplyverifier/env.go @@ -32,10 +32,17 @@ type SupplyCommitView interface { UnspentPrecommits(ctx context.Context, assetSpec asset.Specifier) lfn.Result[supplycommit.PreCommits] - // SupplyCommit returns the latest supply commitment for a given asset - // spec. - SupplyCommit(ctx context.Context, - assetSpec asset.Specifier) supplycommit.RootCommitResp + // FetchStartingCommitment fetches the very first supply commitment of + // an asset group. If no commitment is found, it returns + // ErrCommitmentNotFound. + FetchStartingCommitment(ctx context.Context, + assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) + + // FetchLatestCommitment fetches the latest supply commitment of an + // asset group. If no commitment is found, it returns + // ErrCommitmentNotFound. + FetchLatestCommitment(ctx context.Context, + assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) // FetchCommitmentByOutpoint fetches a supply commitment by its outpoint // and group key. If no commitment is found, it returns @@ -52,12 +59,6 @@ type SupplyCommitView interface { spentOutpoint wire.OutPoint) (*supplycommit.RootCommitment, error) - // FetchStartingCommitment fetches the very first supply commitment of - // an asset group. If no commitment is found, it returns - // ErrCommitmentNotFound. - FetchStartingCommitment(ctx context.Context, - assetSpec asset.Specifier) (*supplycommit.RootCommitment, error) - // InsertSupplyCommit inserts a supply commitment into the database. InsertSupplyCommit(ctx context.Context, assetSpec asset.Specifier, commit supplycommit.RootCommitment, @@ -99,6 +100,17 @@ type Environment struct { // pre-commitments. SupplyCommitView SupplyCommitView + // SupplyTreeView is used to fetch supply leaves by height. + SupplyTreeView SupplyTreeView + + // AssetLookup is used to look up asset information such as asset groups + // and asset metadata. + AssetLookup supplycommit.AssetLookup + + // SupplySyncer is used to retrieve supply commitments from a universe + // server. + SupplySyncer SupplySyncer + // ErrChan is the channel that is used to send errors to the caller. ErrChan chan<- error diff --git a/universe/supplyverifier/events.go b/universe/supplyverifier/events.go new file mode 100644 index 000000000..d8141d699 --- /dev/null +++ b/universe/supplyverifier/events.go @@ -0,0 +1,78 @@ +package supplyverifier + +import ( + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/protofsm" +) + +// Event is a special interface used to create the equivalent of a sum-type, but +// using a "sealed" interface. +type Event interface { + eventSealed() +} + +// Events is a special type constraint that enumerates all the possible protocol +// events. +type Events interface { +} + +// FsmEvent is a type alias for the event type of the supply verifier state +// machine. +type FsmEvent = protofsm.EmittedEvent[Event] + +// InitEvent is the first event that is sent to the state machine. +type InitEvent struct{} + +// eventSealed is a special method that is used to seal the interface. +func (i *InitEvent) eventSealed() {} + +// SyncVerifyEvent is sent to SyncVerifyState to prompt it to sync-verify +// starting from the given outpoint, or from scratch if no outpoint is given. +type SyncVerifyEvent struct { + // SpentCommitOutpoint is an optional outpoint that was spent which + // triggered the need to start syncing from the beginning. If this is + // None, then we will sync from the first supply commitment. + SpentCommitOutpoint fn.Option[wire.OutPoint] +} + +// eventSealed is a special method that is used to seal the interface. +func (e *SyncVerifyEvent) eventSealed() {} + +// WatchOutputsEvent is an event that carries the set of outputs to watch. +type WatchOutputsEvent struct { + // PreCommits is the set of all pre-commitments that should be watched + // for a spend. + PreCommits supplycommit.PreCommits + + // SupplyCommit is the latest known supply commitment that should be + // watched for a spend. + SupplyCommit *supplycommit.RootCommitment +} + +// eventSealed is a special method that is used to seal the interface. +func (e *WatchOutputsEvent) eventSealed() {} + +// SpendEvent is sent in response to an intent to be notified of a spend of an +// outpoint. +type SpendEvent struct { + // SpendDetail is the details of the spend that was observed on-chain. + SpendDetail *chainntnfs.SpendDetail + + // PreCommitments is the set of all pre-commitments that were being + // watched for a spend. + PreCommitments []supplycommit.PreCommitment + + // SpentPreCommitment is the pre-commitment that was spent. This will + // be non-nil only if the spent output was a pre-commitment. + SpentPreCommitment *supplycommit.PreCommitment + + // SpentSupplyCommitment is the supply commitment that was spent. This + // will be non-nil only if the spent output was a supply commitment. + SpentSupplyCommitment *supplycommit.RootCommitment +} + +// eventSealed is a special method that is used to seal the interface. +func (s *SpendEvent) eventSealed() {} diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index a4f31023b..6a0bbb4e0 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -238,6 +238,8 @@ func (m *Manager) startAssetSM(ctx context.Context, AssetSpec: assetSpec, Chain: m.cfg.Chain, SupplyCommitView: m.cfg.SupplyCommitView, + SupplyTreeView: m.cfg.SupplyTreeView, + SupplySyncer: m.cfg.SupplySyncer, ErrChan: m.cfg.ErrChan, QuitChan: m.Quit, } diff --git a/universe/supplyverifier/states.go b/universe/supplyverifier/states.go index 1bc5f04a1..e9017e339 100644 --- a/universe/supplyverifier/states.go +++ b/universe/supplyverifier/states.go @@ -12,17 +12,6 @@ var ( ErrInvalidStateTransition = fmt.Errorf("invalid state transition") ) -// Event is a special interface used to create the equivalent of a sum-type, but -// using a "sealed" interface. -type Event interface { - eventSealed() -} - -// Events is a special type constraint that enumerates all the possible protocol -// events. -type Events interface { -} - // StateTransition is the StateTransition type specific to the supply verifier // state machine. type StateTransition = protofsm.StateTransition[Event, *Environment] @@ -36,6 +25,59 @@ type State interface { String() string } +// InitState is the starting state of the machine. In this state we decide +// whether to start syncing immediately or wait for spends before syncing. +type InitState struct { +} + +// stateSealed is a special method that is used to seal the interface. +func (s *InitState) stateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (s *InitState) IsTerminal() bool { + return false +} + +// String returns the name of the state. +func (s *InitState) String() string { + return "InitState" +} + +// SyncVerifyState is the state where we sync proofs related to a +// supply commitment transaction. +type SyncVerifyState struct{} + +// stateSealed is a special method that is used to seal the interface. +func (s *SyncVerifyState) stateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (s *SyncVerifyState) IsTerminal() bool { + return false +} + +// String returns the name of the state. +func (s *SyncVerifyState) String() string { + return "SyncVerifyState" +} + +// WatchOutputsState waits for one of the watched outputs to be spent. +// If an output is already spent, we transition immediately. +// This state avoids wasted sync polling of universe servers. +type WatchOutputsState struct{} + +// stateSealed is a special method that is used to seal the interface. +func (s *WatchOutputsState) stateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (s *WatchOutputsState) IsTerminal() bool { + return false +} + +// String returns the name of the state. +func (s *WatchOutputsState) String() string { + return "WatchOutputsState" +} + // StateMachine is a state machine that handles verifying the on-chain supply // commitment for a given asset. type StateMachine = protofsm.StateMachine[Event, *Environment] @@ -47,16 +89,6 @@ type Config = protofsm.StateMachineCfg[Event, *Environment] // FsmState is a type alias for the state of the supply verifier state machine. type FsmState = protofsm.State[Event, *Environment] -// FsmEvent is a type alias for the event type of the supply verifier state -// machine. -type FsmEvent = protofsm.EmittedEvent[Event] - // StateSub is a type alias for the state subscriber of the supply verifier // state machine. type StateSub = protofsm.StateSubscriber[Event, *Environment] - -// InitEvent is the first event that is sent to the state machine. -type InitEvent struct{} - -// eventSealed is a special method that is used to seal the interface. -func (i *InitEvent) eventSealed() {} diff --git a/universe/supplyverifier/transitions.go b/universe/supplyverifier/transitions.go new file mode 100644 index 000000000..cfdf94eef --- /dev/null +++ b/universe/supplyverifier/transitions.go @@ -0,0 +1,329 @@ +package supplyverifier + +import ( + "context" + "errors" + "fmt" + "net/url" + + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/universe/supplycommit" + "github.com/lightningnetwork/lnd/chainntnfs" + lfn "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/protofsm" +) + +// ProcessEvent handles the initial state transition for the supply verifier. +func (s *InitState) ProcessEvent(event Event, + env *Environment) (*StateTransition, error) { + + switch event.(type) { + case *InitEvent: + ctx := context.Background() + + // First, we'll query local db for the latest verified supply + // commitment. + latestCommit, err := env.SupplyCommitView.FetchLatestCommitment( + ctx, env.AssetSpec, + ) + switch { + case errors.Is(err, ErrCommitmentNotFound): + // If we don't have a supply commitment in our local db, + // then we will kick things off by syncing supply + // commitment proofs to catch up. + return &StateTransition{ + NextState: &SyncVerifyState{}, + NewEvents: lfn.Some(FsmEvent{ + InternalEvent: []Event{ + &SyncVerifyEvent{}, + }, + }), + }, nil + + case err != nil: + return nil, fmt.Errorf("unable to fetch latest "+ + "verified commitment from db: %w", err) + } + + // If we do have a prior verified commitment, then we'll + // construct a watch event to watch for its spend, and also any + // other un-spent pre-commitments. + preCommits, err := env.SupplyCommitView.UnspentPrecommits( + ctx, env.AssetSpec, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch unspent "+ + "pre-commitments: %w", err) + } + + return &StateTransition{ + NextState: &WatchOutputsState{}, + NewEvents: lfn.Some(FsmEvent{ + InternalEvent: []Event{ + &WatchOutputsEvent{ + PreCommits: preCommits, + SupplyCommit: latestCommit, + }, + }, + }), + }, nil + + default: + return nil, fmt.Errorf("%w: received %T while in %T", + ErrInvalidStateTransition, event, s) + } +} + +// ProcessEvent handles state transitions for the SyncVerifyState. +func (s *SyncVerifyState) ProcessEvent(event Event, + env *Environment) (*StateTransition, error) { + + switch e := event.(type) { + case *SyncVerifyEvent: + ctx := context.Background() + + // Check to ensure that we haven't already processed a supply + // commitment for the spent outpoint, if one was provided. + if e.SpentCommitOutpoint.IsSome() { + spentOutpoint, err := e.SpentCommitOutpoint.UnwrapOrErr( + fmt.Errorf("no outpoint"), + ) + if err != nil { + return nil, err + } + + commit, err := env.SupplyCommitView. + FetchCommitmentBySpentOutpoint( + ctx, env.AssetSpec, spentOutpoint, + ) + switch { + case errors.Is(err, ErrCommitmentNotFound): + // This is the expected case, so we can + // continue. + case err != nil: + return nil, fmt.Errorf("unable to query "+ + "db for commitment: %w", err) + } + + // If we found a commitment, then we've already + // processed this supply commit, so we can + // transition to the watch state. + watchEvent := WatchOutputsEvent{ + SupplyCommit: commit, + } + return &StateTransition{ + NextState: &WatchOutputsState{}, + NewEvents: lfn.Some(FsmEvent{ + InternalEvent: []Event{ + &watchEvent, + }, + }), + }, nil + } + + // If we reach this point, then we need to actually sync pull + // supply commitment(s). + // + // Retrieve latest canonical universe list from the latest + // metadata for the asset group. + metadata, err := supplycommit.FetchLatestAssetMetadata( + ctx, env.AssetLookup, env.AssetSpec, + ) + if err != nil { + return nil, fmt.Errorf("unable to fetch latest asset "+ + "metadata: %w", err) + } + + canonicalUniverses := metadata.CanonicalUniverses.UnwrapOr( + []url.URL{}, + ) + + res, err := env.SupplySyncer.PullSupplyCommitment( + ctx, env.AssetSpec, e.SpentCommitOutpoint, + canonicalUniverses, + ) + if err != nil { + return nil, fmt.Errorf("unable to pull supply "+ + "commitment: %w", err) + } + + // Verify the pulled commitment. + supplyCommit, err := res.FetchResult.UnwrapOrErr( + fmt.Errorf("no commitment found"), + ) + if err != nil { + return nil, err + } + + verifier, err := NewVerifier( + env.Chain, env.SupplyCommitView, env.SupplyTreeView, + ) + if err != nil { + return nil, fmt.Errorf("unable to create verifier: %w", + err) + } + + err = verifier.VerifyCommit( + ctx, env.AssetSpec, supplyCommit.RootCommitment, + supplyCommit.SupplyLeaves, + ) + if err != nil { + return nil, fmt.Errorf("unable to verify supply "+ + "commitment: %w", err) + } + + // Store the verified commitment. + err = env.SupplyCommitView.InsertSupplyCommit( + ctx, env.AssetSpec, supplyCommit.RootCommitment, + supplyCommit.SupplyLeaves, + ) + if err != nil { + return nil, fmt.Errorf("unable to store supply "+ + "commitment: %w", err) + } + + // Now that we've synced and verified the latest commitment, + // we'll transition to the watch state to await spends of this + // commitment. + watchEvent := WatchOutputsEvent{ + SupplyCommit: &supplyCommit.RootCommitment, + } + return &StateTransition{ + NextState: &WatchOutputsState{}, + NewEvents: lfn.Some(FsmEvent{ + InternalEvent: []Event{ + &watchEvent, + }, + }), + }, nil + + case *SpendEvent: + // TODO(ffranr): This is basically the same as SyncVerifyEvent + // but we add a delay before syncing because the issuer may not + // have published the supply commitment yet. + + var spentCommitOutpoint fn.Option[wire.OutPoint] + if e.SpentSupplyCommitment != nil { + spentCommitOutpoint = fn.Some( + e.SpentSupplyCommitment.CommitPoint(), + ) + } + + syncEvent := SyncVerifyEvent{ + SpentCommitOutpoint: spentCommitOutpoint, + } + return &StateTransition{ + NextState: &SyncVerifyState{}, + NewEvents: lfn.Some(FsmEvent{ + InternalEvent: []Event{ + &syncEvent, + }, + }), + }, nil + + default: + return nil, fmt.Errorf("%w: received %T while in %T", + ErrInvalidStateTransition, event, s) + } +} + +// ProcessEvent handles the state transition for the WatchOutputsState. +func (s *WatchOutputsState) ProcessEvent(event Event, + env *Environment) (*StateTransition, error) { + + switch e := event.(type) { + case *WatchOutputsEvent: + preCommits := e.PreCommits + + // If no pre-commitments were provided, then we'll query our + // local view for the set of unspent pre-commitments. + if len(preCommits) == 0 { + var ( + ctx = context.Background() + err error + ) + + preCommits, err = + env.SupplyCommitView.UnspentPrecommits( + ctx, env.AssetSpec, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch "+ + "unspent pre-commitments: %w", err) + } + } + + // Formulate registered spend events for each of the + // pre-commitment outputs that should be watched. + events := make(protofsm.DaemonEventSet, 0, len(preCommits)+1) + for idx := range preCommits { + preCommit := preCommits[idx] + + outpoint := wire.OutPoint{ + Hash: preCommit.MintingTxn.TxHash(), + Index: preCommit.OutIdx, + } + txOut := preCommit.MintingTxn.TxOut[preCommit.OutIdx] + + pc := preCommit + mapper := func(spend *chainntnfs.SpendDetail) Event { + spendEvent := &SpendEvent{ + SpendDetail: spend, + SpentPreCommitment: &pc, + PreCommitments: preCommits, + } + return spendEvent + } + + events = append(events, &protofsm.RegisterSpend[Event]{ + OutPoint: outpoint, + PkScript: txOut.PkScript, + PostSpendEvent: lfn.Some( + protofsm.SpendMapper[Event](mapper), + ), + }) + } + + // If a supply commitment was provided, we'll also register a + // spend event for its output. + if e.SupplyCommit != nil { + outpoint := wire.OutPoint{ + Hash: e.SupplyCommit.Txn.TxHash(), + Index: e.SupplyCommit.TxOutIdx, + } + txOutIdx := e.SupplyCommit.TxOutIdx + txOut := e.SupplyCommit.Txn.TxOut[txOutIdx] + + sc := e.SupplyCommit + mapper := func(spend *chainntnfs.SpendDetail) Event { + return &SpendEvent{ + SpendDetail: spend, + SpentSupplyCommitment: sc, + PreCommitments: preCommits, + } + } + + events = append(events, &protofsm.RegisterSpend[Event]{ + OutPoint: outpoint, + PkScript: txOut.PkScript, + PostSpendEvent: lfn.Some( + protofsm.SpendMapper[Event](mapper), + ), + }) + } + + // Otherwise, we'll transition to the verify state to await + // a spend of one of the outputs we're watching. + return &StateTransition{ + NextState: &SyncVerifyState{}, + NewEvents: lfn.Some(FsmEvent{ + ExternalEvents: events, + }), + }, nil + + default: + return nil, fmt.Errorf("%w: received %T while in %T", + ErrInvalidStateTransition, event, s) + } +} From 1c9da6966128b73ee914ae0b50c327cee9f30b1a Mon Sep 17 00:00:00 2001 From: ffranr Date: Mon, 1 Sep 2025 17:54:36 +0100 Subject: [PATCH 46/51] WIP: supplyverifier: remove state log; we don't need to save state machine state --- universe/supplyverifier/manager.go | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/universe/supplyverifier/manager.go b/universe/supplyverifier/manager.go index 6a0bbb4e0..cba7ae82a 100644 --- a/universe/supplyverifier/manager.go +++ b/universe/supplyverifier/manager.go @@ -38,19 +38,6 @@ type DaemonAdapters interface { Stop() error } -// StateMachineStore is an interface that allows the state machine to persist -// its state across restarts. This is used to track the state of the state -// machine for supply verification. -type StateMachineStore interface { - // CommitState is used to commit the state of the state machine to disk. - CommitState(context.Context, asset.Specifier, State) error - - // FetchState attempts to fetch the state of the state machine for the - // target asset specifier. If the state machine doesn't exist, then a - // default state will be returned. - FetchState(context.Context, asset.Specifier) (State, error) -} - // IssuanceSubscriptions allows verifier state machines to subscribe to // asset group issuance events. type IssuanceSubscriptions interface { @@ -90,11 +77,6 @@ type ManagerCfg struct { // interact with external daemons whilst processing internal events. DaemonAdapters DaemonAdapters - // StateLog is the main state log that is used to track the state of the - // state machine. This is used to persist the state of the state machine - // across restarts. - StateLog StateMachineStore - // ErrChan is the channel that is used to send errors to the caller. ErrChan chan<- error } @@ -244,19 +226,12 @@ func (m *Manager) startAssetSM(ctx context.Context, QuitChan: m.Quit, } - // Before we start the state machine, we'll need to fetch the current - // state from disk, to see if we need to emit any new events. - initialState, err := m.cfg.StateLog.FetchState(ctx, assetSpec) - if err != nil { - return nil, fmt.Errorf("unable to fetch current state: %w", err) - } - // Create a new error reporter for the state machine. errorReporter := NewErrorReporter(assetSpec) fsmCfg := protofsm.StateMachineCfg[Event, *Environment]{ ErrorReporter: &errorReporter, - InitialState: initialState, + InitialState: &InitState{}, Env: env, Daemon: m.cfg.DaemonAdapters, } From 17afcc0b4e3caaf9f3582462506e3f5e4793ed9a Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 15:14:47 +0100 Subject: [PATCH 47/51] tapdb: rename unit test functionality for new term "supply" --- tapdb/asset_minting_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 9093d309a..9091a764d 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1835,8 +1835,8 @@ func TestTapscriptTreeManager(t *testing.T) { loadTapscriptTreeChecked(t, ctx, assetStore, tree5, tree5Hash) } -// storeMintAnchorUniCommitment stores a mint anchor commitment in the DB. -func storeMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, +// storeSupplyPreCommit stores a mint anchor commitment in the DB. +func storeSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, taprootInternalKey keychain.KeyDescriptor, groupKey []byte) { @@ -1869,9 +1869,9 @@ func storeMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, _ = assetStore.db.ExecTx(ctx, &writeTxOpts, upsertMintAnchorPreCommit) } -// assertMintAnchorUniCommitment is a helper function that reads a mint anchor +// assertSupplyPreCommit is a helper function that reads a mint anchor // commitment from the DB and asserts that it matches the expected values. -func assertMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, +func assertSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, preCommitInternalKey keychain.KeyDescriptor, groupPubKeyBytes []byte) { @@ -1914,10 +1914,10 @@ func assertMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, require.Equal(t, groupPubKeyBytes, preCommit.GroupKey) } -// TestUpsertMintAnchorUniCommitment tests the UpsertMintAnchorUniCommitment -// FetchMintAnchorUniCommitment and SQL queries. In particular, it tests that -// upsert works correctly. -func TestUpsertMintAnchorUniCommitment(t *testing.T) { +// TestUpsertSupplyPreCommit tests the UpsertSupplyPreCommit and +// FetchSupplyPreCommits SQL queries. In particular, it tests that upsert works +// correctly. +func TestUpsertSupplyPreCommit(t *testing.T) { t.Parallel() ctx := context.Background() @@ -1955,13 +1955,13 @@ func TestUpsertMintAnchorUniCommitment(t *testing.T) { // Upsert a mint anchor commitment for the batch. txOutputIndex := int32(2) - storeMintAnchorUniCommitment( + storeSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, preCommitInternalKey, groupPubKeyBytes, ) // Retrieve and inspect the mint anchor commitment we just inserted. - assertMintAnchorUniCommitment( + assertSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, preCommitInternalKey, groupPubKeyBytes, ) @@ -1970,12 +1970,12 @@ func TestUpsertMintAnchorUniCommitment(t *testing.T) { // overwrite the existing one. internalKey2, _ := test.RandKeyDesc(t) - storeMintAnchorUniCommitment( + storeSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKeyBytes, ) - assertMintAnchorUniCommitment( + assertSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKeyBytes, ) @@ -1985,12 +1985,12 @@ func TestUpsertMintAnchorUniCommitment(t *testing.T) { groupPubKey2 := test.RandPubKey(t) groupPubKey2Bytes := groupPubKey2.SerializeCompressed() - storeMintAnchorUniCommitment( + storeSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKey2Bytes, ) - assertMintAnchorUniCommitment( + assertSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKey2Bytes, ) From ba63071d38be353992463966c2c1777a352037bf Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 12:36:59 +0100 Subject: [PATCH 48/51] tapdb: rename table to supply_pre_commits and unique on outpoint Rename table `mint_anchor_uni_commitments` to `supply_pre_commits` to align with current "supply" terminology. Drop the NOT NULL constraint on `batch_id` so the table can also store pre-commit outputs from peer-issued assets, not just those minted locally. Also, remove the unique index on (batch_id, tx_output_index) and add unique index on outpoint. --- tapdb/asset_minting_test.go | 36 +++++++++---- tapdb/migrations.go | 2 +- tapdb/sqlc/assets.sql.go | 28 +++++------ ...uni_commitments_nullable_batch_id.down.sql | 50 +++++++++++++++++++ ...r_uni_commitments_nullable_batch_id.up.sql | 47 +++++++++++++++++ tapdb/sqlc/models.go | 20 ++++---- tapdb/sqlc/querier.go | 4 +- tapdb/sqlc/queries/assets.sql | 26 +++++----- tapdb/sqlc/queries/supply_commit.sql | 4 +- tapdb/sqlc/schemas/generated_schema.sql | 48 ++++++++++-------- tapdb/sqlc/supply_commit.sql.go | 4 +- tapdb/universe.go | 3 ++ 12 files changed, 196 insertions(+), 76 deletions(-) create mode 100644 tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.down.sql create mode 100644 tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.up.sql diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 9091a764d..a9fa053a8 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1838,7 +1838,8 @@ func TestTapscriptTreeManager(t *testing.T) { // storeSupplyPreCommit stores a mint anchor commitment in the DB. func storeSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, - taprootInternalKey keychain.KeyDescriptor, groupKey []byte) { + taprootInternalKey keychain.KeyDescriptor, groupKey []byte, + outpoint wire.OutPoint) { ctx := context.Background() @@ -1854,12 +1855,16 @@ func storeSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, }) require.NoError(t, err) + opBytes, err := encodeOutpoint(outpoint) + require.NoError(t, err) + _, err = q.UpsertMintAnchorUniCommitment( ctx, sqlc.UpsertMintAnchorUniCommitmentParams{ BatchKey: batchKey, TxOutputIndex: txOutputIndex, TaprootInternalKeyID: internalKeyID, GroupKey: groupKey, + Outpoint: opBytes, }, ) require.NoError(t, err) @@ -1948,16 +1953,27 @@ func TestUpsertSupplyPreCommit(t *testing.T) { }, ) + // Define pre-commit outpoint for the batch mint anchor tx. + txOutputIndex := int32(2) + txidStr := mintingBatch.GenesisPacket.FundedPsbt.Pkt.UnsignedTx.TxID() + + txid, err := chainhash.NewHashFromStr(txidStr) + require.NoError(t, err) + + preCommitOutpoint := wire.OutPoint{ + Hash: *txid, + Index: uint32(txOutputIndex), + } + // Serialize keys into bytes for easier handling. preCommitInternalKey, _ := test.RandKeyDesc(t) groupPubKeyBytes := group.GroupPubKey.SerializeCompressed() // Upsert a mint anchor commitment for the batch. - txOutputIndex := int32(2) storeSupplyPreCommit( - t, *assetStore, batchKey, txOutputIndex, - preCommitInternalKey, groupPubKeyBytes, + t, *assetStore, batchKey, txOutputIndex, preCommitInternalKey, + groupPubKeyBytes, preCommitOutpoint, ) // Retrieve and inspect the mint anchor commitment we just inserted. @@ -1966,13 +1982,13 @@ func TestUpsertSupplyPreCommit(t *testing.T) { preCommitInternalKey, groupPubKeyBytes, ) - // Upsert-ing a new taproot internal key for the same batch should - // overwrite the existing one. + // Upsert-ing a new taproot internal key for the same pre-commit + // outpoint should overwrite the existing one. internalKey2, _ := test.RandKeyDesc(t) storeSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, - groupPubKeyBytes, + groupPubKeyBytes, preCommitOutpoint, ) assertSupplyPreCommit( @@ -1980,14 +1996,14 @@ func TestUpsertSupplyPreCommit(t *testing.T) { groupPubKeyBytes, ) - // Upsert-ing a new group key for the same batch should overwrite the - // existing one. + // Upsert-ing a new group key for the same pre-commit outpoint should + // overwrite the existing one. groupPubKey2 := test.RandPubKey(t) groupPubKey2Bytes := groupPubKey2.SerializeCompressed() storeSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, - groupPubKey2Bytes, + groupPubKey2Bytes, preCommitOutpoint, ) assertSupplyPreCommit( diff --git a/tapdb/migrations.go b/tapdb/migrations.go index 4666eb5c5..6c3f948b6 100644 --- a/tapdb/migrations.go +++ b/tapdb/migrations.go @@ -24,7 +24,7 @@ const ( // daemon. // // NOTE: This MUST be updated when a new migration is added. - LatestMigrationVersion = 45 + LatestMigrationVersion = 46 ) // DatabaseBackend is an interface that contains all methods our different diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 10026b20a..695363f63 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -1602,24 +1602,24 @@ func (q *Queries) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow const FetchMintAnchorUniCommitment = `-- name: FetchMintAnchorUniCommitment :many SELECT - mint_anchor_uni_commitments.id, - mint_anchor_uni_commitments.batch_id, - mint_anchor_uni_commitments.tx_output_index, - mint_anchor_uni_commitments.group_key, - mint_anchor_uni_commitments.spent_by, + precommits.id, + precommits.batch_id, + precommits.tx_output_index, + precommits.group_key, + precommits.spent_by, batch_internal_keys.raw_key AS batch_key, - mint_anchor_uni_commitments.taproot_internal_key_id, + precommits.taproot_internal_key_id, taproot_internal_keys.key_id, taproot_internal_keys.raw_key, taproot_internal_keys.key_family, taproot_internal_keys.key_index -FROM mint_anchor_uni_commitments +FROM supply_pre_commits AS precommits JOIN internal_keys taproot_internal_keys - ON mint_anchor_uni_commitments.taproot_internal_key_id = taproot_internal_keys.key_id + ON precommits.taproot_internal_key_id = taproot_internal_keys.key_id LEFT JOIN asset_minting_batches batches - ON mint_anchor_uni_commitments.batch_id = batches.batch_id + ON precommits.batch_id = batches.batch_id LEFT JOIN internal_keys batch_internal_keys ON batches.batch_id = batch_internal_keys.key_id WHERE ( (batch_internal_keys.raw_key = $1 OR $1 IS NULL) AND - (mint_anchor_uni_commitments.group_key = $2 OR $2 IS NULL) AND + (precommits.group_key = $2 OR $2 IS NULL) AND (taproot_internal_keys.raw_key = $3 OR $3 IS NULL) ) ` @@ -1632,7 +1632,7 @@ type FetchMintAnchorUniCommitmentParams struct { type FetchMintAnchorUniCommitmentRow struct { ID int64 - BatchID int32 + BatchID sql.NullInt32 TxOutputIndex int32 GroupKey []byte SpentBy sql.NullInt64 @@ -1641,7 +1641,7 @@ type FetchMintAnchorUniCommitmentRow struct { InternalKey InternalKey } -// Fetch records from the mint_anchor_uni_commitments table with optional +// Fetch records from the supply_pre_commits table with optional // filtering. func (q *Queries) FetchMintAnchorUniCommitment(ctx context.Context, arg FetchMintAnchorUniCommitmentParams) ([]FetchMintAnchorUniCommitmentRow, error) { rows, err := q.db.QueryContext(ctx, FetchMintAnchorUniCommitment, arg.BatchKey, arg.GroupKey, arg.TaprootInternalKeyRaw) @@ -3245,7 +3245,7 @@ WITH target_batch AS ( FROM internal_keys keys WHERE keys.raw_key = $6 ) -INSERT INTO mint_anchor_uni_commitments ( +INSERT INTO supply_pre_commits ( batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint ) VALUES ( @@ -3269,7 +3269,7 @@ type UpsertMintAnchorUniCommitmentParams struct { BatchKey []byte } -// Upsert a record into the mint_anchor_uni_commitments table. +// Upsert a record into the supply_pre_commits table. // If a record with the same batch ID and tx output index already exists, update // the existing record. Otherwise, insert a new record. func (q *Queries) UpsertMintAnchorUniCommitment(ctx context.Context, arg UpsertMintAnchorUniCommitmentParams) (int64, error) { diff --git a/tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.down.sql b/tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.down.sql new file mode 100644 index 000000000..79d924934 --- /dev/null +++ b/tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.down.sql @@ -0,0 +1,50 @@ +-- Revert batch_id back to NOT NULL in supply_pre_commits table and +-- rename back to mint_anchor_uni_commitments. Since SQLite doesn't support +-- ALTER COLUMN, we need to recreate the table. + +-- Create a new table with the original structure (batch_id NOT NULL). +CREATE TABLE mint_anchor_uni_commitments ( + id INTEGER PRIMARY KEY, + + -- The ID of the minting batch this universe commitment relates to. + batch_id INTEGER NOT NULL REFERENCES asset_minting_batches(batch_id), + + -- The index of the mint batch anchor transaction pre-commitment output. + tx_output_index INTEGER NOT NULL, + + -- The Taproot output internal key for the pre-commitment output. + group_key BLOB, + + -- The taproot internal key ID reference. + taproot_internal_key_id BIGINT REFERENCES internal_keys(key_id) NOT NULL, + + -- Reference to supply commitments. + spent_by BIGINT REFERENCES supply_commitments(commit_id), + + -- The outpoint for this commitment. + outpoint BLOB +); + +-- Copy all existing data from the old table to the new table. +-- This will fail if there are any NULL batch_id values, which is expected +-- behavior for a down migration that removes nullable support. +INSERT INTO mint_anchor_uni_commitments ( + id, batch_id, tx_output_index, group_key, taproot_internal_key_id, spent_by, + outpoint +) +SELECT + id, batch_id, tx_output_index, group_key, taproot_internal_key_id, spent_by, + outpoint +FROM supply_pre_commits +WHERE batch_id IS NOT NULL; + +-- DROP old indexes before dropping the table. +DROP INDEX IF EXISTS supply_pre_commits_unique_outpoint; + +-- Drop the old table. +DROP TABLE supply_pre_commits; + +-- Recreate the indexes. +CREATE INDEX mint_anchor_uni_commitments_outpoint_idx + ON mint_anchor_uni_commitments(outpoint) + WHERE outpoint IS NOT NULL; diff --git a/tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.up.sql b/tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.up.sql new file mode 100644 index 000000000..546d3f2d6 --- /dev/null +++ b/tapdb/sqlc/migrations/000046_mint_anchor_uni_commitments_nullable_batch_id.up.sql @@ -0,0 +1,47 @@ +-- Make batch_id nullable in mint_anchor_uni_commitments table and rename to +-- supply_pre_commits. Since SQLite doesn't support ALTER COLUMN, we need +-- to recreate the table. + +-- Create a new table with the desired structure (batch_id nullable). +CREATE TABLE supply_pre_commits ( + id INTEGER PRIMARY KEY, + + -- The ID of the minting batch this universe commitment relates to. + -- Now nullable to allow universe commitments without a specific batch. + batch_id INTEGER REFERENCES asset_minting_batches(batch_id), + + -- The index of the mint batch anchor transaction pre-commitment output. + tx_output_index INTEGER NOT NULL, + + -- The Taproot output internal key for the pre-commitment output. + group_key BLOB, + + -- The taproot internal key ID reference. + taproot_internal_key_id BIGINT REFERENCES internal_keys(key_id) NOT NULL, + + -- Reference to supply commitments. + spent_by BIGINT REFERENCES supply_commitments(commit_id), + + -- The outpoint for this commitment. + outpoint BLOB NOT NULL CHECK(length(outpoint) > 0) +); + +-- Copy all existing data from the old table to the new table. +INSERT INTO supply_pre_commits ( + id, batch_id, tx_output_index, group_key, taproot_internal_key_id, spent_by, + outpoint +) +SELECT + id, batch_id, tx_output_index, group_key, taproot_internal_key_id, spent_by, + outpoint +FROM mint_anchor_uni_commitments; + +-- Drop the old index before dropping the table. +DROP INDEX IF EXISTS mint_anchor_uni_commitments_outpoint_idx; + +-- Drop the old table. +DROP TABLE mint_anchor_uni_commitments; + +-- Create a unique index on outpoint. +CREATE UNIQUE INDEX supply_pre_commits_unique_outpoint + ON supply_pre_commits(outpoint); diff --git a/tapdb/sqlc/models.go b/tapdb/sqlc/models.go index fb1e3aeb2..209193159 100644 --- a/tapdb/sqlc/models.go +++ b/tapdb/sqlc/models.go @@ -304,16 +304,6 @@ type ManagedUtxo struct { RootVersion sql.NullInt16 } -type MintAnchorUniCommitment struct { - ID int64 - BatchID int32 - TxOutputIndex int32 - GroupKey []byte - TaprootInternalKeyID int64 - SpentBy sql.NullInt64 - Outpoint []byte -} - type MssmtNode struct { HashKey []byte LHashKey []byte @@ -415,6 +405,16 @@ type SupplyCommitment struct { SpentCommitment sql.NullInt64 } +type SupplyPreCommit struct { + ID int64 + BatchID sql.NullInt32 + TxOutputIndex int32 + GroupKey []byte + TaprootInternalKeyID int64 + SpentBy sql.NullInt64 + Outpoint []byte +} + type SupplySyncerPushLog struct { ID int64 GroupKey []byte diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index f7c3ce19a..a72a59416 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -90,7 +90,7 @@ type Querier interface { FetchInternalKeyLocator(ctx context.Context, rawKey []byte) (FetchInternalKeyLocatorRow, error) FetchManagedUTXO(ctx context.Context, arg FetchManagedUTXOParams) (FetchManagedUTXORow, error) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow, error) - // Fetch records from the mint_anchor_uni_commitments table with optional + // Fetch records from the supply_pre_commits table with optional // filtering. FetchMintAnchorUniCommitment(ctx context.Context, arg FetchMintAnchorUniCommitmentParams) ([]FetchMintAnchorUniCommitmentRow, error) FetchMintingBatch(ctx context.Context, rawKey []byte) (FetchMintingBatchRow, error) @@ -234,7 +234,7 @@ type Querier interface { UpsertGenesisPoint(ctx context.Context, prevOut []byte) (int64, error) UpsertInternalKey(ctx context.Context, arg UpsertInternalKeyParams) (int64, error) UpsertManagedUTXO(ctx context.Context, arg UpsertManagedUTXOParams) (int64, error) - // Upsert a record into the mint_anchor_uni_commitments table. + // Upsert a record into the supply_pre_commits table. // If a record with the same batch ID and tx output index already exists, update // the existing record. Otherwise, insert a new record. UpsertMintAnchorUniCommitment(ctx context.Context, arg UpsertMintAnchorUniCommitmentParams) (int64, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 9628f3032..222226933 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1063,7 +1063,7 @@ JOIN genesis_assets ORDER BY assets_meta.meta_id; -- name: UpsertMintAnchorUniCommitment :one --- Upsert a record into the mint_anchor_uni_commitments table. +-- Upsert a record into the supply_pre_commits table. -- If a record with the same batch ID and tx output index already exists, update -- the existing record. Otherwise, insert a new record. WITH target_batch AS ( @@ -1073,7 +1073,7 @@ WITH target_batch AS ( FROM internal_keys keys WHERE keys.raw_key = @batch_key ) -INSERT INTO mint_anchor_uni_commitments ( +INSERT INTO supply_pre_commits ( batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint ) VALUES ( @@ -1088,26 +1088,26 @@ ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET RETURNING id; -- name: FetchMintAnchorUniCommitment :many --- Fetch records from the mint_anchor_uni_commitments table with optional +-- Fetch records from the supply_pre_commits table with optional -- filtering. SELECT - mint_anchor_uni_commitments.id, - mint_anchor_uni_commitments.batch_id, - mint_anchor_uni_commitments.tx_output_index, - mint_anchor_uni_commitments.group_key, - mint_anchor_uni_commitments.spent_by, + precommits.id, + precommits.batch_id, + precommits.tx_output_index, + precommits.group_key, + precommits.spent_by, batch_internal_keys.raw_key AS batch_key, - mint_anchor_uni_commitments.taproot_internal_key_id, + precommits.taproot_internal_key_id, sqlc.embed(taproot_internal_keys) -FROM mint_anchor_uni_commitments +FROM supply_pre_commits AS precommits JOIN internal_keys taproot_internal_keys - ON mint_anchor_uni_commitments.taproot_internal_key_id = taproot_internal_keys.key_id + ON precommits.taproot_internal_key_id = taproot_internal_keys.key_id LEFT JOIN asset_minting_batches batches - ON mint_anchor_uni_commitments.batch_id = batches.batch_id + ON precommits.batch_id = batches.batch_id LEFT JOIN internal_keys batch_internal_keys ON batches.batch_id = batch_internal_keys.key_id WHERE ( (batch_internal_keys.raw_key = sqlc.narg('batch_key') OR sqlc.narg('batch_key') IS NULL) AND - (mint_anchor_uni_commitments.group_key = sqlc.narg('group_key') OR sqlc.narg('group_key') IS NULL) AND + (precommits.group_key = sqlc.narg('group_key') OR sqlc.narg('group_key') IS NULL) AND (taproot_internal_keys.raw_key = sqlc.narg('taproot_internal_key_raw') OR sqlc.narg('taproot_internal_key_raw') IS NULL) ); diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 6f0cfa899..a6102dbc8 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -212,7 +212,7 @@ SELECT mac.group_key, mint_txn.block_height, mint_txn.raw_tx -FROM mint_anchor_uni_commitments mac +FROM supply_pre_commits mac JOIN asset_minting_batches amb ON mac.batch_id = amb.batch_id JOIN genesis_points gp ON amb.genesis_id = gp.genesis_id JOIN chain_txns mint_txn ON gp.anchor_tx_id = mint_txn.txn_id @@ -225,7 +225,7 @@ WHERE -- name: MarkPreCommitmentSpentByOutpoint :exec -- Mark a specific pre-commitment output as spent by its outpoint. -UPDATE mint_anchor_uni_commitments +UPDATE supply_pre_commits SET spent_by = @spent_by_commit_id WHERE outpoint = @outpoint AND spent_by IS NULL; diff --git a/tapdb/sqlc/schemas/generated_schema.sql b/tapdb/sqlc/schemas/generated_schema.sql index 0d686d16e..c0fab733c 100644 --- a/tapdb/sqlc/schemas/generated_schema.sql +++ b/tapdb/sqlc/schemas/generated_schema.sql @@ -624,28 +624,6 @@ CREATE TABLE managed_utxos ( lease_expiry TIMESTAMP , root_version SMALLINT); -CREATE TABLE mint_anchor_uni_commitments ( - id INTEGER PRIMARY KEY, - - -- The ID of the minting batch this universe commitment relates to. - batch_id INTEGER NOT NULL REFERENCES asset_minting_batches(batch_id), - - -- The index of the mint batch anchor transaction pre-commitment output. - tx_output_index INTEGER NOT NULL, - - -- The Taproot output internal key for the pre-commitment output. - group_key BLOB -, taproot_internal_key_id -BIGINT REFERENCES internal_keys(key_id) -NOT NULL, spent_by BIGINT REFERENCES supply_commitments(commit_id), outpoint BLOB); - -CREATE INDEX mint_anchor_uni_commitments_outpoint_idx - ON mint_anchor_uni_commitments(outpoint) - WHERE outpoint IS NOT NULL; - -CREATE UNIQUE INDEX mint_anchor_uni_commitments_unique - ON mint_anchor_uni_commitments (batch_id, tx_output_index); - CREATE TABLE mssmt_nodes ( -- hash_key is the hash key by which we reference all nodes. hash_key BLOB NOT NULL, @@ -892,6 +870,32 @@ CREATE UNIQUE INDEX supply_commitments_outpoint_uk CREATE INDEX supply_commitments_spent_commitment_idx ON supply_commitments(spent_commitment); +CREATE TABLE supply_pre_commits ( + id INTEGER PRIMARY KEY, + + -- The ID of the minting batch this universe commitment relates to. + -- Now nullable to allow universe commitments without a specific batch. + batch_id INTEGER REFERENCES asset_minting_batches(batch_id), + + -- The index of the mint batch anchor transaction pre-commitment output. + tx_output_index INTEGER NOT NULL, + + -- The Taproot output internal key for the pre-commitment output. + group_key BLOB, + + -- The taproot internal key ID reference. + taproot_internal_key_id BIGINT REFERENCES internal_keys(key_id) NOT NULL, + + -- Reference to supply commitments. + spent_by BIGINT REFERENCES supply_commitments(commit_id), + + -- The outpoint for this commitment. + outpoint BLOB NOT NULL CHECK(length(outpoint) > 0) +); + +CREATE UNIQUE INDEX supply_pre_commits_unique_outpoint + ON supply_pre_commits(outpoint); + CREATE TABLE supply_syncer_push_log ( id INTEGER PRIMARY KEY, diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index a72fae381..4fa76e931 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -115,7 +115,7 @@ SELECT mac.group_key, mint_txn.block_height, mint_txn.raw_tx -FROM mint_anchor_uni_commitments mac +FROM supply_pre_commits mac JOIN asset_minting_batches amb ON mac.batch_id = amb.batch_id JOIN genesis_points gp ON amb.genesis_id = gp.genesis_id JOIN chain_txns mint_txn ON gp.anchor_tx_id = mint_txn.txn_id @@ -311,7 +311,7 @@ func (q *Queries) LinkDanglingSupplyUpdateEvents(ctx context.Context, arg LinkDa } const MarkPreCommitmentSpentByOutpoint = `-- name: MarkPreCommitmentSpentByOutpoint :exec -UPDATE mint_anchor_uni_commitments +UPDATE supply_pre_commits SET spent_by = $1 WHERE outpoint = $2 AND spent_by IS NULL diff --git a/tapdb/universe.go b/tapdb/universe.go index 54678177a..435e62795 100644 --- a/tapdb/universe.go +++ b/tapdb/universe.go @@ -794,6 +794,9 @@ func universeUpsertProofLeaf(ctx context.Context, dbTx BaseUniverseStore, return nil, fmt.Errorf("unable to decode proof: %w", err) } + // Upsert into the DB: the genesis point, asset genesis, + // group key reveal, and the anchoring transaction for the issuance or + // transfer. assetGenID, err := upsertAssetGen( ctx, dbTx, leaf.Genesis, leaf.GroupKey, &leafProof, ) From a02ba50e61c53aa4715d7a3212041f823ac3f79e Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 12:50:52 +0100 Subject: [PATCH 49/51] tapdb: rename FetchMintAnchorUniCommitment to FetchSupplyPreCommits Rename SQL query to better align with "supply" terminology. --- tapdb/asset_minting.go | 22 ++--- tapdb/asset_minting_test.go | 6 +- tapdb/sqlc/assets.sql.go | 156 +++++++++++++++++----------------- tapdb/sqlc/querier.go | 6 +- tapdb/sqlc/queries/assets.sql | 2 +- 5 files changed, 96 insertions(+), 96 deletions(-) diff --git a/tapdb/asset_minting.go b/tapdb/asset_minting.go index 16d9a97c1..d55bc1ac9 100644 --- a/tapdb/asset_minting.go +++ b/tapdb/asset_minting.go @@ -116,9 +116,9 @@ type ( // database ID. ProofUpdateByID = sqlc.UpsertAssetProofByIDParams - // FetchPreCommitParams is a type alias for the params used to fetch - // mint anchor pre-commitments. - FetchPreCommitParams = sqlc.FetchMintAnchorUniCommitmentParams + // FetchPreCommitsParams is a type alias for the params used to fetch + // mint anchor supply pre-commitments. + FetchPreCommitsParams = sqlc.FetchSupplyPreCommitsParams // FetchAssetID is used to fetch the primary key ID of an asset, by // outpoint and tweaked script key. @@ -248,10 +248,10 @@ type PendingAssetStore interface { FetchAssetMetaForAsset(ctx context.Context, assetID []byte) (sqlc.FetchAssetMetaForAssetRow, error) - // FetchMintAnchorUniCommitment fetches mint anchor pre-commitments. - FetchMintAnchorUniCommitment(ctx context.Context, - arg FetchPreCommitParams) ( - []sqlc.FetchMintAnchorUniCommitmentRow, error) + // FetchSupplyPreCommits fetches mint anchor pre-commitments. + FetchSupplyPreCommits(ctx context.Context, + arg FetchPreCommitsParams) ( + []sqlc.FetchSupplyPreCommitsRow, error) // UpsertMintAnchorUniCommitment inserts a new or updates an existing // mint anchor uni commitment on disk. @@ -1347,8 +1347,8 @@ func marshalMintingBatch(ctx context.Context, q PendingAssetStore, // the pre-commitment output index from the database. var preCommitOut fn.Option[tapgarden.PreCommitmentOutput] if dbBatch.UniverseCommitments { - fetchRes, err := q.FetchMintAnchorUniCommitment( - ctx, FetchPreCommitParams{ + fetchRes, err := q.FetchSupplyPreCommits( + ctx, FetchPreCommitsParams{ BatchKey: dbBatch.RawKey, }, ) @@ -1545,8 +1545,8 @@ func (a *AssetMintingStore) FetchDelegationKey(ctx context.Context, readOpts := NewAssetStoreReadTx() dbErr := a.db.ExecTx(ctx, &readOpts, func(q PendingAssetStore) error { - fetchRow, err := q.FetchMintAnchorUniCommitment( - ctx, FetchPreCommitParams{ + fetchRow, err := q.FetchSupplyPreCommits( + ctx, FetchPreCommitsParams{ GroupKey: groupKeyBytes, }, ) diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index a9fa053a8..3aed394cc 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1883,10 +1883,10 @@ func assertSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, ctx := context.Background() readOpts := NewAssetStoreReadTx() - var preCommit *sqlc.FetchMintAnchorUniCommitmentRow + var preCommit *sqlc.FetchSupplyPreCommitsRow readMintAnchorCommitment := func(q PendingAssetStore) error { - fetchRes, err := q.FetchMintAnchorUniCommitment( - ctx, FetchPreCommitParams{ + fetchRes, err := q.FetchSupplyPreCommits( + ctx, FetchPreCommitsParams{ BatchKey: batchKey, }, ) diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 695363f63..1942c4b09 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -1600,84 +1600,6 @@ func (q *Queries) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow return items, nil } -const FetchMintAnchorUniCommitment = `-- name: FetchMintAnchorUniCommitment :many -SELECT - precommits.id, - precommits.batch_id, - precommits.tx_output_index, - precommits.group_key, - precommits.spent_by, - batch_internal_keys.raw_key AS batch_key, - precommits.taproot_internal_key_id, - taproot_internal_keys.key_id, taproot_internal_keys.raw_key, taproot_internal_keys.key_family, taproot_internal_keys.key_index -FROM supply_pre_commits AS precommits - JOIN internal_keys taproot_internal_keys - ON precommits.taproot_internal_key_id = taproot_internal_keys.key_id - LEFT JOIN asset_minting_batches batches - ON precommits.batch_id = batches.batch_id - LEFT JOIN internal_keys batch_internal_keys - ON batches.batch_id = batch_internal_keys.key_id -WHERE ( - (batch_internal_keys.raw_key = $1 OR $1 IS NULL) AND - (precommits.group_key = $2 OR $2 IS NULL) AND - (taproot_internal_keys.raw_key = $3 OR $3 IS NULL) -) -` - -type FetchMintAnchorUniCommitmentParams struct { - BatchKey []byte - GroupKey []byte - TaprootInternalKeyRaw []byte -} - -type FetchMintAnchorUniCommitmentRow struct { - ID int64 - BatchID sql.NullInt32 - TxOutputIndex int32 - GroupKey []byte - SpentBy sql.NullInt64 - BatchKey []byte - TaprootInternalKeyID int64 - InternalKey InternalKey -} - -// Fetch records from the supply_pre_commits table with optional -// filtering. -func (q *Queries) FetchMintAnchorUniCommitment(ctx context.Context, arg FetchMintAnchorUniCommitmentParams) ([]FetchMintAnchorUniCommitmentRow, error) { - rows, err := q.db.QueryContext(ctx, FetchMintAnchorUniCommitment, arg.BatchKey, arg.GroupKey, arg.TaprootInternalKeyRaw) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchMintAnchorUniCommitmentRow - for rows.Next() { - var i FetchMintAnchorUniCommitmentRow - if err := rows.Scan( - &i.ID, - &i.BatchID, - &i.TxOutputIndex, - &i.GroupKey, - &i.SpentBy, - &i.BatchKey, - &i.TaprootInternalKeyID, - &i.InternalKey.KeyID, - &i.InternalKey.RawKey, - &i.InternalKey.KeyFamily, - &i.InternalKey.KeyIndex, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const FetchMintingBatch = `-- name: FetchMintingBatch :one WITH target_batch AS ( -- This CTE is used to fetch the ID of a batch, based on the serialized @@ -2025,6 +1947,84 @@ func (q *Queries) FetchSeedlingsForBatch(ctx context.Context, rawKey []byte) ([] return items, nil } +const FetchSupplyPreCommits = `-- name: FetchSupplyPreCommits :many +SELECT + precommits.id, + precommits.batch_id, + precommits.tx_output_index, + precommits.group_key, + precommits.spent_by, + batch_internal_keys.raw_key AS batch_key, + precommits.taproot_internal_key_id, + taproot_internal_keys.key_id, taproot_internal_keys.raw_key, taproot_internal_keys.key_family, taproot_internal_keys.key_index +FROM supply_pre_commits AS precommits + JOIN internal_keys taproot_internal_keys + ON precommits.taproot_internal_key_id = taproot_internal_keys.key_id + LEFT JOIN asset_minting_batches batches + ON precommits.batch_id = batches.batch_id + LEFT JOIN internal_keys batch_internal_keys + ON batches.batch_id = batch_internal_keys.key_id +WHERE ( + (batch_internal_keys.raw_key = $1 OR $1 IS NULL) AND + (precommits.group_key = $2 OR $2 IS NULL) AND + (taproot_internal_keys.raw_key = $3 OR $3 IS NULL) +) +` + +type FetchSupplyPreCommitsParams struct { + BatchKey []byte + GroupKey []byte + TaprootInternalKeyRaw []byte +} + +type FetchSupplyPreCommitsRow struct { + ID int64 + BatchID sql.NullInt32 + TxOutputIndex int32 + GroupKey []byte + SpentBy sql.NullInt64 + BatchKey []byte + TaprootInternalKeyID int64 + InternalKey InternalKey +} + +// Fetch records from the supply_pre_commits table with optional +// filtering. +func (q *Queries) FetchSupplyPreCommits(ctx context.Context, arg FetchSupplyPreCommitsParams) ([]FetchSupplyPreCommitsRow, error) { + rows, err := q.db.QueryContext(ctx, FetchSupplyPreCommits, arg.BatchKey, arg.GroupKey, arg.TaprootInternalKeyRaw) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FetchSupplyPreCommitsRow + for rows.Next() { + var i FetchSupplyPreCommitsRow + if err := rows.Scan( + &i.ID, + &i.BatchID, + &i.TxOutputIndex, + &i.GroupKey, + &i.SpentBy, + &i.BatchKey, + &i.TaprootInternalKeyID, + &i.InternalKey.KeyID, + &i.InternalKey.RawKey, + &i.InternalKey.KeyFamily, + &i.InternalKey.KeyIndex, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const FetchTapscriptTree = `-- name: FetchTapscriptTree :many WITH tree_info AS ( -- This CTE is used to fetch all edges that link the given tapscript tree diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index a72a59416..a67b7776f 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -90,9 +90,6 @@ type Querier interface { FetchInternalKeyLocator(ctx context.Context, rawKey []byte) (FetchInternalKeyLocatorRow, error) FetchManagedUTXO(ctx context.Context, arg FetchManagedUTXOParams) (FetchManagedUTXORow, error) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow, error) - // Fetch records from the supply_pre_commits table with optional - // filtering. - FetchMintAnchorUniCommitment(ctx context.Context, arg FetchMintAnchorUniCommitmentParams) ([]FetchMintAnchorUniCommitmentRow, error) FetchMintingBatch(ctx context.Context, rawKey []byte) (FetchMintingBatchRow, error) FetchMintingBatchesByInverseState(ctx context.Context, batchState int16) ([]FetchMintingBatchesByInverseStateRow, error) FetchMultiverseRoot(ctx context.Context, namespaceRoot string) (FetchMultiverseRootRow, error) @@ -103,6 +100,9 @@ type Querier interface { FetchSeedlingID(ctx context.Context, arg FetchSeedlingIDParams) (int64, error) FetchSeedlingsForBatch(ctx context.Context, rawKey []byte) ([]FetchSeedlingsForBatchRow, error) FetchSupplyCommit(ctx context.Context, groupKey []byte) (FetchSupplyCommitRow, error) + // Fetch records from the supply_pre_commits table with optional + // filtering. + FetchSupplyPreCommits(ctx context.Context, arg FetchSupplyPreCommitsParams) ([]FetchSupplyPreCommitsRow, error) // Fetches all push log entries for a given asset group, ordered by // creation time with the most recent entries first. FetchSupplySyncerPushLogs(ctx context.Context, groupKey []byte) ([]SupplySyncerPushLog, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 222226933..2146e2915 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1087,7 +1087,7 @@ ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET outpoint = EXCLUDED.outpoint RETURNING id; --- name: FetchMintAnchorUniCommitment :many +-- name: FetchSupplyPreCommits :many -- Fetch records from the supply_pre_commits table with optional -- filtering. SELECT From fd84f6f0f085fbaaaa24c9cf94b35f5ee59379d6 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 13:38:02 +0100 Subject: [PATCH 50/51] tapdb: rename UpsertMintAnchorUniCommitment to UpsertSupplyPreCommit Rename SQL query to better align with "suppl" terminology. --- tapdb/asset_minting.go | 20 +++---- tapdb/asset_minting_test.go | 4 +- tapdb/sqlc/assets.sql.go | 98 +++++++++++++++++------------------ tapdb/sqlc/querier.go | 8 +-- tapdb/sqlc/queries/assets.sql | 2 +- tapdb/supply_commit_test.go | 4 +- 6 files changed, 68 insertions(+), 68 deletions(-) diff --git a/tapdb/asset_minting.go b/tapdb/asset_minting.go index d55bc1ac9..4ba5448ae 100644 --- a/tapdb/asset_minting.go +++ b/tapdb/asset_minting.go @@ -132,9 +132,9 @@ type ( // disk. NewAssetMeta = sqlc.UpsertAssetMetaParams - // MintAnchorUniCommitParams wraps the params needed to insert a new - // mint anchor uni commitment on disk. - MintAnchorUniCommitParams = sqlc.UpsertMintAnchorUniCommitmentParams + // UpsertPreCommitParams wraps the params needed to insert a new + // supply pre-commit on disk. + UpsertPreCommitParams = sqlc.UpsertSupplyPreCommitParams ) // PendingAssetStore is a sub-set of the main sqlc.Querier interface that @@ -253,10 +253,10 @@ type PendingAssetStore interface { arg FetchPreCommitsParams) ( []sqlc.FetchSupplyPreCommitsRow, error) - // UpsertMintAnchorUniCommitment inserts a new or updates an existing + // UpsertSupplyPreCommit inserts a new or updates an existing // mint anchor uni commitment on disk. - UpsertMintAnchorUniCommitment(ctx context.Context, - arg MintAnchorUniCommitParams) (int64, error) + UpsertSupplyPreCommit(ctx context.Context, + arg UpsertPreCommitParams) (int64, error) } var ( @@ -448,8 +448,8 @@ func insertMintAnchorTx(ctx context.Context, q PendingAssetStore, return fmt.Errorf("unable to encode outpoint: %w", err) } - _, err = q.UpsertMintAnchorUniCommitment( - ctx, MintAnchorUniCommitParams{ + _, err = q.UpsertSupplyPreCommit( + ctx, UpsertPreCommitParams{ BatchKey: rawBatchKey, TxOutputIndex: int32(preCommitOut.OutIdx), TaprootInternalKeyID: internalKeyID, @@ -1644,8 +1644,8 @@ func upsertPreCommit(ctx context.Context, q PendingAssetStore, return fmt.Errorf("unable to encode outpoint: %w", err) } - _, err = q.UpsertMintAnchorUniCommitment( - ctx, MintAnchorUniCommitParams{ + _, err = q.UpsertSupplyPreCommit( + ctx, UpsertPreCommitParams{ BatchKey: batchKey, TxOutputIndex: int32(preCommit.OutIdx), TaprootInternalKeyID: internalKeyID, diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 3aed394cc..b4584cb14 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1858,8 +1858,8 @@ func storeSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, opBytes, err := encodeOutpoint(outpoint) require.NoError(t, err) - _, err = q.UpsertMintAnchorUniCommitment( - ctx, sqlc.UpsertMintAnchorUniCommitmentParams{ + _, err = q.UpsertSupplyPreCommit( + ctx, UpsertPreCommitParams{ BatchKey: batchKey, TxOutputIndex: txOutputIndex, TaprootInternalKeyID: internalKeyID, diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 1942c4b09..b0f746895 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -3237,55 +3237,6 @@ func (q *Queries) UpsertManagedUTXO(ctx context.Context, arg UpsertManagedUTXOPa return utxo_id, err } -const UpsertMintAnchorUniCommitment = `-- name: UpsertMintAnchorUniCommitment :one -WITH target_batch AS ( - -- This CTE is used to fetch the ID of a batch, based on the serialized - -- internal key associated with the batch. - SELECT keys.key_id AS batch_id - FROM internal_keys keys - WHERE keys.raw_key = $6 -) -INSERT INTO supply_pre_commits ( - batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint -) -VALUES ( - (SELECT batch_id FROM target_batch), $1, - $2, $3, $4, $5 -) -ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET - -- The following fields are updated if a conflict occurs. - taproot_internal_key_id = EXCLUDED.taproot_internal_key_id, - group_key = EXCLUDED.group_key, - outpoint = EXCLUDED.outpoint -RETURNING id -` - -type UpsertMintAnchorUniCommitmentParams struct { - TxOutputIndex int32 - TaprootInternalKeyID int64 - GroupKey []byte - SpentBy sql.NullInt64 - Outpoint []byte - BatchKey []byte -} - -// Upsert a record into the supply_pre_commits table. -// If a record with the same batch ID and tx output index already exists, update -// the existing record. Otherwise, insert a new record. -func (q *Queries) UpsertMintAnchorUniCommitment(ctx context.Context, arg UpsertMintAnchorUniCommitmentParams) (int64, error) { - row := q.db.QueryRowContext(ctx, UpsertMintAnchorUniCommitment, - arg.TxOutputIndex, - arg.TaprootInternalKeyID, - arg.GroupKey, - arg.SpentBy, - arg.Outpoint, - arg.BatchKey, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - const UpsertScriptKey = `-- name: UpsertScriptKey :one INSERT INTO script_keys ( internal_key_id, tweaked_script_key, tweak, key_type @@ -3333,6 +3284,55 @@ func (q *Queries) UpsertScriptKey(ctx context.Context, arg UpsertScriptKeyParams return script_key_id, err } +const UpsertSupplyPreCommit = `-- name: UpsertSupplyPreCommit :one +WITH target_batch AS ( + -- This CTE is used to fetch the ID of a batch, based on the serialized + -- internal key associated with the batch. + SELECT keys.key_id AS batch_id + FROM internal_keys keys + WHERE keys.raw_key = $6 +) +INSERT INTO supply_pre_commits ( + batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint +) +VALUES ( + (SELECT batch_id FROM target_batch), $1, + $2, $3, $4, $5 +) +ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET + -- The following fields are updated if a conflict occurs. + taproot_internal_key_id = EXCLUDED.taproot_internal_key_id, + group_key = EXCLUDED.group_key, + outpoint = EXCLUDED.outpoint +RETURNING id +` + +type UpsertSupplyPreCommitParams struct { + TxOutputIndex int32 + TaprootInternalKeyID int64 + GroupKey []byte + SpentBy sql.NullInt64 + Outpoint []byte + BatchKey []byte +} + +// Upsert a record into the supply_pre_commits table. +// If a record with the same batch ID and tx output index already exists, update +// the existing record. Otherwise, insert a new record. +func (q *Queries) UpsertSupplyPreCommit(ctx context.Context, arg UpsertSupplyPreCommitParams) (int64, error) { + row := q.db.QueryRowContext(ctx, UpsertSupplyPreCommit, + arg.TxOutputIndex, + arg.TaprootInternalKeyID, + arg.GroupKey, + arg.SpentBy, + arg.Outpoint, + arg.BatchKey, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + const UpsertTapscriptTreeEdge = `-- name: UpsertTapscriptTreeEdge :one INSERT INTO tapscript_edges ( root_hash_id, node_index, raw_node_id diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index a67b7776f..4ac51c8d9 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -234,10 +234,6 @@ type Querier interface { UpsertGenesisPoint(ctx context.Context, prevOut []byte) (int64, error) UpsertInternalKey(ctx context.Context, arg UpsertInternalKeyParams) (int64, error) UpsertManagedUTXO(ctx context.Context, arg UpsertManagedUTXOParams) (int64, error) - // Upsert a record into the supply_pre_commits table. - // If a record with the same batch ID and tx output index already exists, update - // the existing record. Otherwise, insert a new record. - UpsertMintAnchorUniCommitment(ctx context.Context, arg UpsertMintAnchorUniCommitmentParams) (int64, error) UpsertMultiverseLeaf(ctx context.Context, arg UpsertMultiverseLeafParams) (int64, error) UpsertMultiverseRoot(ctx context.Context, arg UpsertMultiverseRootParams) (int64, error) UpsertRootNode(ctx context.Context, arg UpsertRootNodeParams) error @@ -245,6 +241,10 @@ type Querier interface { // Return the ID of the state that was actually set (either inserted or updated), // and the latest commitment ID that was set. UpsertSupplyCommitStateMachine(ctx context.Context, arg UpsertSupplyCommitStateMachineParams) (UpsertSupplyCommitStateMachineRow, error) + // Upsert a record into the supply_pre_commits table. + // If a record with the same batch ID and tx output index already exists, update + // the existing record. Otherwise, insert a new record. + UpsertSupplyPreCommit(ctx context.Context, arg UpsertSupplyPreCommitParams) (int64, error) UpsertTapscriptTreeEdge(ctx context.Context, arg UpsertTapscriptTreeEdgeParams) (int64, error) UpsertTapscriptTreeNode(ctx context.Context, rawNode []byte) (int64, error) UpsertTapscriptTreeRootHash(ctx context.Context, arg UpsertTapscriptTreeRootHashParams) (int64, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 2146e2915..6efa80cac 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1062,7 +1062,7 @@ JOIN genesis_assets ON genesis_assets.meta_data_id = assets_meta.meta_id ORDER BY assets_meta.meta_id; --- name: UpsertMintAnchorUniCommitment :one +-- name: UpsertSupplyPreCommit :one -- Upsert a record into the supply_pre_commits table. -- If a record with the same batch ID and tx output index already exists, update -- the existing record. Otherwise, insert a new record. diff --git a/tapdb/supply_commit_test.go b/tapdb/supply_commit_test.go index f9f821839..4cf63ea2a 100644 --- a/tapdb/supply_commit_test.go +++ b/tapdb/supply_commit_test.go @@ -242,8 +242,8 @@ func (h *supplyCommitTestHarness) addTestMintAnchorUniCommitment( err = wire.WriteOutPoint(&outpointBuf, 0, 0, &outpoint) require.NoError(h.t, err) - anchorCommitID, err := h.db.UpsertMintAnchorUniCommitment( - h.ctx, sqlc.UpsertMintAnchorUniCommitmentParams{ + anchorCommitID, err := h.db.UpsertSupplyPreCommit( + h.ctx, UpsertPreCommitParams{ BatchKey: batchKeyBytes, TxOutputIndex: txOutputIndex, TaprootInternalKeyID: internalKeyID, From 87ca58f48d060817b619f79ad540d6af4a0cf2f1 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 15:15:02 +0100 Subject: [PATCH 51/51] WIP --- tapdb/sqlc/assets.sql.go | 22 ++++++++++++---------- tapdb/sqlc/querier.go | 4 ++-- tapdb/sqlc/queries/assets.sql | 22 ++++++++++++---------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index b0f746895..2ad99f65c 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -3286,23 +3286,25 @@ func (q *Queries) UpsertScriptKey(ctx context.Context, arg UpsertScriptKeyParams const UpsertSupplyPreCommit = `-- name: UpsertSupplyPreCommit :one WITH target_batch AS ( - -- This CTE is used to fetch the ID of a batch, based on the serialized - -- internal key associated with the batch. SELECT keys.key_id AS batch_id - FROM internal_keys keys + FROM internal_keys AS keys WHERE keys.raw_key = $6 ) INSERT INTO supply_pre_commits ( - batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint + batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, + outpoint ) VALUES ( - (SELECT batch_id FROM target_batch), $1, - $2, $3, $4, $5 + (SELECT batch_id FROM target_batch), $1, + $2, $3, $4, + $5 ) -ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET - -- The following fields are updated if a conflict occurs. +ON CONFLICT(outpoint) DO UPDATE SET + batch_id = EXCLUDED.batch_id, + tx_output_index = EXCLUDED.tx_output_index, taproot_internal_key_id = EXCLUDED.taproot_internal_key_id, group_key = EXCLUDED.group_key, + spent_by = EXCLUDED.spent_by, outpoint = EXCLUDED.outpoint RETURNING id ` @@ -3317,8 +3319,8 @@ type UpsertSupplyPreCommitParams struct { } // Upsert a record into the supply_pre_commits table. -// If a record with the same batch ID and tx output index already exists, update -// the existing record. Otherwise, insert a new record. +// If a record with the same outpoint exists, update it; otherwise insert a new +// record. func (q *Queries) UpsertSupplyPreCommit(ctx context.Context, arg UpsertSupplyPreCommitParams) (int64, error) { row := q.db.QueryRowContext(ctx, UpsertSupplyPreCommit, arg.TxOutputIndex, diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 4ac51c8d9..01d6fb4d4 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -242,8 +242,8 @@ type Querier interface { // and the latest commitment ID that was set. UpsertSupplyCommitStateMachine(ctx context.Context, arg UpsertSupplyCommitStateMachineParams) (UpsertSupplyCommitStateMachineRow, error) // Upsert a record into the supply_pre_commits table. - // If a record with the same batch ID and tx output index already exists, update - // the existing record. Otherwise, insert a new record. + // If a record with the same outpoint exists, update it; otherwise insert a new + // record. UpsertSupplyPreCommit(ctx context.Context, arg UpsertSupplyPreCommitParams) (int64, error) UpsertTapscriptTreeEdge(ctx context.Context, arg UpsertTapscriptTreeEdgeParams) (int64, error) UpsertTapscriptTreeNode(ctx context.Context, rawNode []byte) (int64, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 6efa80cac..2ff036d7e 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1064,26 +1064,28 @@ ORDER BY assets_meta.meta_id; -- name: UpsertSupplyPreCommit :one -- Upsert a record into the supply_pre_commits table. --- If a record with the same batch ID and tx output index already exists, update --- the existing record. Otherwise, insert a new record. +-- If a record with the same outpoint exists, update it; otherwise insert a new +-- record. WITH target_batch AS ( - -- This CTE is used to fetch the ID of a batch, based on the serialized - -- internal key associated with the batch. SELECT keys.key_id AS batch_id - FROM internal_keys keys + FROM internal_keys AS keys WHERE keys.raw_key = @batch_key ) INSERT INTO supply_pre_commits ( - batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint + batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, + outpoint ) VALUES ( - (SELECT batch_id FROM target_batch), @tx_output_index, - @taproot_internal_key_id, @group_key, sqlc.narg('spent_by'), sqlc.narg('outpoint') + (SELECT batch_id FROM target_batch), @tx_output_index, + @taproot_internal_key_id, @group_key, sqlc.narg('spent_by'), + sqlc.narg('outpoint') ) -ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET - -- The following fields are updated if a conflict occurs. +ON CONFLICT(outpoint) DO UPDATE SET + batch_id = EXCLUDED.batch_id, + tx_output_index = EXCLUDED.tx_output_index, taproot_internal_key_id = EXCLUDED.taproot_internal_key_id, group_key = EXCLUDED.group_key, + spent_by = EXCLUDED.spent_by, outpoint = EXCLUDED.outpoint RETURNING id;