From c0be0a3ffef964f34d12126cfc4f18f4a9009750 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 8 May 2026 01:51:28 -0700 Subject: [PATCH 1/9] Reward authority rotation primitive (PRs 222 + 225 + 228 bundled) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three logical chunks bundled onto a single branch off main: PR1 — Schema (mjp-reward-pools-schema): - New core_reward_pools table keyed by Solana RM pubkey, with a text[] authorities column (gin-indexed for @> containment). - launchpad_authority_rm seed table mapping every known launchpad- derived per-mint claim authority → its Solana reward manager state account. Used by both the migration backfill and PR2's wire-compat replay logic. - core_rewards.rewards_manager_pubkey FK column; claim_authorities column dropped (reads now alias coalesce(p.authorities, '{}') via LEFT JOIN on core_reward_pools). - Backfill creates one pool per RM (per-RM authority union across all rewards referencing it via launchpad lookup). Rows whose authorities don't match any launchpad RM stay NULL — there are no synthetic mig_ identifiers. - Live finalizeCreateReward (legacy proto shape, brief PR1-only window) does launchpad lookup → bind to existing pool only; never upserts. NULL fallback if no match or pool missing. PR2 — CometBFT transactions (mjp-reward-pools-tx): - New body+signature envelope: Tx { TxBody body; signatures[] }. Reward and RewardPool messages move to the new shape. - CreateRewardPool / SetRewardPoolAuthorities txs gated by real-RM-shape pubkey + signer ∈ current pool authorities. - CreateReward proto reserves tags 4-6 (former claim_authorities, deadline, signature) and uses tag 7 for rewards_manager_pubkey. DeleteReward reserves tags 2-3. - Wire-compat layer (rewards_legacy.go): legacy bytes are REJECTED at CheckTx/ProcessProposal (no new legacy txs accepted) but ACCEPTED at FinalizeBlock for block-sync replay of historical chain state. Replay uses launchpad lookup to bind legacy rewards to the same RM the migration produced. - Defense-in-depth re-validation at finalize for both pool txs (block-sync replay skips ProcessProposal / CheckTx). PR3 — Validator endpoint cutover (mjp-reward-pools-endpoints): - GetRewardAttestation restored from the #215 kill-switch. Auth check uses dbReward.ClaimAuthorities, which is sourced from coalesce(p.authorities, '{}') — so rotating an authority out via SetRewardPoolAuthorities immediately revokes attestation rights. RewardClaim.RewardAddress is intentionally NOT set (Solana reward manager program expects 2-piece RewardID: Specifier disbursement_id). - GetRewardSenderAttestation / GetDeleteRewardSenderAttestation dispatch by RM: pool-gated if pool exists, else fall back to the legacy validator/AAO trust set (AUDIO path). - AUDIO RM denylist on validateRewardsManagerPubkey: prevents an attacker from creating a pool for the AUDIO RM and inheriting AUDIO sender attestations. Per-env constants in pkg/core/config/rewards.go (dev/prod populated; stage left empty intentionally). Co-Authored-By: Claude Opus 4.7 --- examples/rewards/main.go | 33 +- pkg/api/core/v1/service.pb.go | 221 +- pkg/api/core/v1/types.pb.go | 2086 ++++++++++++----- pkg/api/core/v1/v1connect/service.connect.go | 29 + pkg/common/legacy_reward_signing.go | 69 + pkg/common/proto.go | 48 +- pkg/common/proto_test.go | 166 ++ pkg/common/reward_signing.go | 69 - pkg/core/config/rewards.go | 44 + pkg/core/db/models.go | 39 +- pkg/core/db/reads.sql.go | 293 ++- .../db/sql/migrations/00033_reward_pools.sql | 244 ++ pkg/core/db/sql/reads.sql | 98 +- pkg/core/db/sql/writes.sql | 39 +- pkg/core/db/writes.sql.go | 108 +- pkg/core/server/abci.go | 9 + pkg/core/server/connect.go | 243 +- pkg/core/server/reward_pools.go | 252 ++ pkg/core/server/reward_pools_test.go | 95 + pkg/core/server/rewards.go | 220 +- pkg/core/server/rewards_legacy.go | 202 ++ pkg/core/server/rewards_legacy_test.go | 226 ++ pkg/core/server/state_sync.go | 2 + pkg/integration_tests/12_rewards_test.go | 143 +- pkg/integration_tests/13_reward_pools_test.go | 291 +++ pkg/rewards/reward_pool.go | 29 + pkg/rewards/reward_pool_test.go | 75 + pkg/sdk/rewards/rewards.go | 142 +- proto/core/v1/service.proto | 1 + proto/core/v1/types.proto | 120 +- 30 files changed, 4525 insertions(+), 1111 deletions(-) create mode 100644 pkg/common/legacy_reward_signing.go create mode 100644 pkg/common/proto_test.go delete mode 100644 pkg/common/reward_signing.go create mode 100644 pkg/core/db/sql/migrations/00033_reward_pools.sql create mode 100644 pkg/core/server/reward_pools.go create mode 100644 pkg/core/server/reward_pools_test.go create mode 100644 pkg/core/server/rewards_legacy.go create mode 100644 pkg/core/server/rewards_legacy_test.go create mode 100644 pkg/integration_tests/13_reward_pools_test.go create mode 100644 pkg/rewards/reward_pool.go create mode 100644 pkg/rewards/reward_pool_test.go diff --git a/examples/rewards/main.go b/examples/rewards/main.go index 350062a1..390b5e21 100644 --- a/examples/rewards/main.go +++ b/examples/rewards/main.go @@ -38,15 +38,32 @@ func main() { currentHeight := resp.Msg.ChainInfo.CurrentHeight deadline := currentHeight + 100 + // Replace with the actual Solana reward manager pubkey for the mint + // you're issuing rewards under (base58, 32 bytes). + rewardsManagerPubkey := os.Getenv("REWARDS_MANAGER_PUBKEY") + if rewardsManagerPubkey == "" { + log.Fatalf("REWARDS_MANAGER_PUBKEY environment variable is not set") + } + + // First-class CreateReward requires an existing pool keyed by the + // reward manager pubkey. Create one — fail loudly on any error so the + // next call doesn't proceed against a broken setup. If the pool was + // created in a previous run, this will surface as a "pool already + // exists" error and the example needs to be rerun against a fresh RM + // pubkey (or the existing pool's tx hash recorded for the reuse path). + if _, err := oap.Rewards.CreateRewardPool(context.Background(), &v1.CreateRewardPool{ + RewardsManagerPubkey: rewardsManagerPubkey, + Authorities: []string{oap.Address()}, + }, deadline); err != nil { + log.Fatalf("Failed to create reward pool: %v", err) + } + reward, err := oap.Rewards.CreateReward(context.Background(), &v1.CreateReward{ - RewardId: "reward1", - Name: "Test Reward 1", - Amount: 1000, - ClaimAuthorities: []*v1.ClaimAuthority{ - {Address: oap.Address(), Name: "Alec"}, - }, - DeadlineBlockHeight: deadline, - }) + RewardId: "reward1", + Name: "Test Reward 1", + Amount: 1000, + RewardsManagerPubkey: rewardsManagerPubkey, + }, deadline) if err != nil { log.Fatalf("Failed to create reward: %v", err) } diff --git a/pkg/api/core/v1/service.pb.go b/pkg/api/core/v1/service.pb.go index ef0b0cd2..64dac3a6 100644 --- a/pkg/api/core/v1/service.pb.go +++ b/pkg/api/core/v1/service.pb.go @@ -25,7 +25,7 @@ var file_core_v1_service_proto_rawDesc = []byte{ 0x0a, 0x15, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x13, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x84, 0x11, 0x0a, 0x0b, 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xd6, 0x11, 0x0a, 0x0b, 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, @@ -123,49 +123,54 @@ var file_core_v1_service_proto_rawDesc = []byte{ 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x77, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x31, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x1d, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x77, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x2a, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x20, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x30, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, - 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x1d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, - 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, - 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x0c, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1c, 0x2e, 0x63, 0x6f, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x1d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, + 0x43, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, + 0x43, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, + 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1c, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x33, 0x5a, 0x31, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, - 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, - 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x33, + 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x70, 0x65, + 0x6e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x75, + 0x64, 0x69, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var file_core_v1_service_proto_goTypes = []interface{}{ @@ -188,37 +193,39 @@ var file_core_v1_service_proto_goTypes = []interface{}{ (*GetPIERequest)(nil), // 16: core.v1.GetPIERequest (*GetRewardRequest)(nil), // 17: core.v1.GetRewardRequest (*GetRewardsRequest)(nil), // 18: core.v1.GetRewardsRequest - (*GetRewardAttestationRequest)(nil), // 19: core.v1.GetRewardAttestationRequest - (*GetRewardSenderAttestationRequest)(nil), // 20: core.v1.GetRewardSenderAttestationRequest - (*GetDeleteRewardSenderAttestationRequest)(nil), // 21: core.v1.GetDeleteRewardSenderAttestationRequest - (*GetStreamURLsRequest)(nil), // 22: core.v1.GetStreamURLsRequest - (*GetUploadByCIDRequest)(nil), // 23: core.v1.GetUploadByCIDRequest - (*StreamBlocksRequest)(nil), // 24: core.v1.StreamBlocksRequest - (*PingResponse)(nil), // 25: core.v1.PingResponse - (*GetHealthResponse)(nil), // 26: core.v1.GetHealthResponse - (*GetStatusResponse)(nil), // 27: core.v1.GetStatusResponse - (*GetNodeInfoResponse)(nil), // 28: core.v1.GetNodeInfoResponse - (*GetBlockResponse)(nil), // 29: core.v1.GetBlockResponse - (*GetBlocksResponse)(nil), // 30: core.v1.GetBlocksResponse - (*GetTransactionResponse)(nil), // 31: core.v1.GetTransactionResponse - (*SendTransactionResponse)(nil), // 32: core.v1.SendTransactionResponse - (*ForwardTransactionResponse)(nil), // 33: core.v1.ForwardTransactionResponse - (*GetRegistrationAttestationResponse)(nil), // 34: core.v1.GetRegistrationAttestationResponse - (*GetDeregistrationAttestationResponse)(nil), // 35: core.v1.GetDeregistrationAttestationResponse - (*GetStoredSnapshotsResponse)(nil), // 36: core.v1.GetStoredSnapshotsResponse - (*GetSlashAttestationResponse)(nil), // 37: core.v1.GetSlashAttestationResponse - (*GetSlashAttestationsResponse)(nil), // 38: core.v1.GetSlashAttestationsResponse - (*GetERNResponse)(nil), // 39: core.v1.GetERNResponse - (*GetMEADResponse)(nil), // 40: core.v1.GetMEADResponse - (*GetPIEResponse)(nil), // 41: core.v1.GetPIEResponse - (*GetRewardResponse)(nil), // 42: core.v1.GetRewardResponse - (*GetRewardsResponse)(nil), // 43: core.v1.GetRewardsResponse - (*GetRewardAttestationResponse)(nil), // 44: core.v1.GetRewardAttestationResponse - (*GetRewardSenderAttestationResponse)(nil), // 45: core.v1.GetRewardSenderAttestationResponse - (*GetDeleteRewardSenderAttestationResponse)(nil), // 46: core.v1.GetDeleteRewardSenderAttestationResponse - (*GetStreamURLsResponse)(nil), // 47: core.v1.GetStreamURLsResponse - (*GetUploadByCIDResponse)(nil), // 48: core.v1.GetUploadByCIDResponse - (*StreamBlocksResponse)(nil), // 49: core.v1.StreamBlocksResponse + (*GetRewardPoolRequest)(nil), // 19: core.v1.GetRewardPoolRequest + (*GetRewardAttestationRequest)(nil), // 20: core.v1.GetRewardAttestationRequest + (*GetRewardSenderAttestationRequest)(nil), // 21: core.v1.GetRewardSenderAttestationRequest + (*GetDeleteRewardSenderAttestationRequest)(nil), // 22: core.v1.GetDeleteRewardSenderAttestationRequest + (*GetStreamURLsRequest)(nil), // 23: core.v1.GetStreamURLsRequest + (*GetUploadByCIDRequest)(nil), // 24: core.v1.GetUploadByCIDRequest + (*StreamBlocksRequest)(nil), // 25: core.v1.StreamBlocksRequest + (*PingResponse)(nil), // 26: core.v1.PingResponse + (*GetHealthResponse)(nil), // 27: core.v1.GetHealthResponse + (*GetStatusResponse)(nil), // 28: core.v1.GetStatusResponse + (*GetNodeInfoResponse)(nil), // 29: core.v1.GetNodeInfoResponse + (*GetBlockResponse)(nil), // 30: core.v1.GetBlockResponse + (*GetBlocksResponse)(nil), // 31: core.v1.GetBlocksResponse + (*GetTransactionResponse)(nil), // 32: core.v1.GetTransactionResponse + (*SendTransactionResponse)(nil), // 33: core.v1.SendTransactionResponse + (*ForwardTransactionResponse)(nil), // 34: core.v1.ForwardTransactionResponse + (*GetRegistrationAttestationResponse)(nil), // 35: core.v1.GetRegistrationAttestationResponse + (*GetDeregistrationAttestationResponse)(nil), // 36: core.v1.GetDeregistrationAttestationResponse + (*GetStoredSnapshotsResponse)(nil), // 37: core.v1.GetStoredSnapshotsResponse + (*GetSlashAttestationResponse)(nil), // 38: core.v1.GetSlashAttestationResponse + (*GetSlashAttestationsResponse)(nil), // 39: core.v1.GetSlashAttestationsResponse + (*GetERNResponse)(nil), // 40: core.v1.GetERNResponse + (*GetMEADResponse)(nil), // 41: core.v1.GetMEADResponse + (*GetPIEResponse)(nil), // 42: core.v1.GetPIEResponse + (*GetRewardResponse)(nil), // 43: core.v1.GetRewardResponse + (*GetRewardsResponse)(nil), // 44: core.v1.GetRewardsResponse + (*GetRewardPoolResponse)(nil), // 45: core.v1.GetRewardPoolResponse + (*GetRewardAttestationResponse)(nil), // 46: core.v1.GetRewardAttestationResponse + (*GetRewardSenderAttestationResponse)(nil), // 47: core.v1.GetRewardSenderAttestationResponse + (*GetDeleteRewardSenderAttestationResponse)(nil), // 48: core.v1.GetDeleteRewardSenderAttestationResponse + (*GetStreamURLsResponse)(nil), // 49: core.v1.GetStreamURLsResponse + (*GetUploadByCIDResponse)(nil), // 50: core.v1.GetUploadByCIDResponse + (*StreamBlocksResponse)(nil), // 51: core.v1.StreamBlocksResponse } var file_core_v1_service_proto_depIdxs = []int32{ 0, // 0: core.v1.CoreService.Ping:input_type -> core.v1.PingRequest @@ -240,39 +247,41 @@ var file_core_v1_service_proto_depIdxs = []int32{ 16, // 16: core.v1.CoreService.GetPIE:input_type -> core.v1.GetPIERequest 17, // 17: core.v1.CoreService.GetReward:input_type -> core.v1.GetRewardRequest 18, // 18: core.v1.CoreService.GetRewards:input_type -> core.v1.GetRewardsRequest - 19, // 19: core.v1.CoreService.GetRewardAttestation:input_type -> core.v1.GetRewardAttestationRequest - 20, // 20: core.v1.CoreService.GetRewardSenderAttestation:input_type -> core.v1.GetRewardSenderAttestationRequest - 21, // 21: core.v1.CoreService.GetDeleteRewardSenderAttestation:input_type -> core.v1.GetDeleteRewardSenderAttestationRequest - 22, // 22: core.v1.CoreService.GetStreamURLs:input_type -> core.v1.GetStreamURLsRequest - 23, // 23: core.v1.CoreService.GetUploadByCID:input_type -> core.v1.GetUploadByCIDRequest - 24, // 24: core.v1.CoreService.StreamBlocks:input_type -> core.v1.StreamBlocksRequest - 25, // 25: core.v1.CoreService.Ping:output_type -> core.v1.PingResponse - 26, // 26: core.v1.CoreService.GetHealth:output_type -> core.v1.GetHealthResponse - 27, // 27: core.v1.CoreService.GetStatus:output_type -> core.v1.GetStatusResponse - 28, // 28: core.v1.CoreService.GetNodeInfo:output_type -> core.v1.GetNodeInfoResponse - 29, // 29: core.v1.CoreService.GetBlock:output_type -> core.v1.GetBlockResponse - 30, // 30: core.v1.CoreService.GetBlocks:output_type -> core.v1.GetBlocksResponse - 31, // 31: core.v1.CoreService.GetTransaction:output_type -> core.v1.GetTransactionResponse - 32, // 32: core.v1.CoreService.SendTransaction:output_type -> core.v1.SendTransactionResponse - 33, // 33: core.v1.CoreService.ForwardTransaction:output_type -> core.v1.ForwardTransactionResponse - 34, // 34: core.v1.CoreService.GetRegistrationAttestation:output_type -> core.v1.GetRegistrationAttestationResponse - 35, // 35: core.v1.CoreService.GetDeregistrationAttestation:output_type -> core.v1.GetDeregistrationAttestationResponse - 36, // 36: core.v1.CoreService.GetStoredSnapshots:output_type -> core.v1.GetStoredSnapshotsResponse - 37, // 37: core.v1.CoreService.GetSlashAttestation:output_type -> core.v1.GetSlashAttestationResponse - 38, // 38: core.v1.CoreService.GetSlashAttestations:output_type -> core.v1.GetSlashAttestationsResponse - 39, // 39: core.v1.CoreService.GetERN:output_type -> core.v1.GetERNResponse - 40, // 40: core.v1.CoreService.GetMEAD:output_type -> core.v1.GetMEADResponse - 41, // 41: core.v1.CoreService.GetPIE:output_type -> core.v1.GetPIEResponse - 42, // 42: core.v1.CoreService.GetReward:output_type -> core.v1.GetRewardResponse - 43, // 43: core.v1.CoreService.GetRewards:output_type -> core.v1.GetRewardsResponse - 44, // 44: core.v1.CoreService.GetRewardAttestation:output_type -> core.v1.GetRewardAttestationResponse - 45, // 45: core.v1.CoreService.GetRewardSenderAttestation:output_type -> core.v1.GetRewardSenderAttestationResponse - 46, // 46: core.v1.CoreService.GetDeleteRewardSenderAttestation:output_type -> core.v1.GetDeleteRewardSenderAttestationResponse - 47, // 47: core.v1.CoreService.GetStreamURLs:output_type -> core.v1.GetStreamURLsResponse - 48, // 48: core.v1.CoreService.GetUploadByCID:output_type -> core.v1.GetUploadByCIDResponse - 49, // 49: core.v1.CoreService.StreamBlocks:output_type -> core.v1.StreamBlocksResponse - 25, // [25:50] is the sub-list for method output_type - 0, // [0:25] is the sub-list for method input_type + 19, // 19: core.v1.CoreService.GetRewardPool:input_type -> core.v1.GetRewardPoolRequest + 20, // 20: core.v1.CoreService.GetRewardAttestation:input_type -> core.v1.GetRewardAttestationRequest + 21, // 21: core.v1.CoreService.GetRewardSenderAttestation:input_type -> core.v1.GetRewardSenderAttestationRequest + 22, // 22: core.v1.CoreService.GetDeleteRewardSenderAttestation:input_type -> core.v1.GetDeleteRewardSenderAttestationRequest + 23, // 23: core.v1.CoreService.GetStreamURLs:input_type -> core.v1.GetStreamURLsRequest + 24, // 24: core.v1.CoreService.GetUploadByCID:input_type -> core.v1.GetUploadByCIDRequest + 25, // 25: core.v1.CoreService.StreamBlocks:input_type -> core.v1.StreamBlocksRequest + 26, // 26: core.v1.CoreService.Ping:output_type -> core.v1.PingResponse + 27, // 27: core.v1.CoreService.GetHealth:output_type -> core.v1.GetHealthResponse + 28, // 28: core.v1.CoreService.GetStatus:output_type -> core.v1.GetStatusResponse + 29, // 29: core.v1.CoreService.GetNodeInfo:output_type -> core.v1.GetNodeInfoResponse + 30, // 30: core.v1.CoreService.GetBlock:output_type -> core.v1.GetBlockResponse + 31, // 31: core.v1.CoreService.GetBlocks:output_type -> core.v1.GetBlocksResponse + 32, // 32: core.v1.CoreService.GetTransaction:output_type -> core.v1.GetTransactionResponse + 33, // 33: core.v1.CoreService.SendTransaction:output_type -> core.v1.SendTransactionResponse + 34, // 34: core.v1.CoreService.ForwardTransaction:output_type -> core.v1.ForwardTransactionResponse + 35, // 35: core.v1.CoreService.GetRegistrationAttestation:output_type -> core.v1.GetRegistrationAttestationResponse + 36, // 36: core.v1.CoreService.GetDeregistrationAttestation:output_type -> core.v1.GetDeregistrationAttestationResponse + 37, // 37: core.v1.CoreService.GetStoredSnapshots:output_type -> core.v1.GetStoredSnapshotsResponse + 38, // 38: core.v1.CoreService.GetSlashAttestation:output_type -> core.v1.GetSlashAttestationResponse + 39, // 39: core.v1.CoreService.GetSlashAttestations:output_type -> core.v1.GetSlashAttestationsResponse + 40, // 40: core.v1.CoreService.GetERN:output_type -> core.v1.GetERNResponse + 41, // 41: core.v1.CoreService.GetMEAD:output_type -> core.v1.GetMEADResponse + 42, // 42: core.v1.CoreService.GetPIE:output_type -> core.v1.GetPIEResponse + 43, // 43: core.v1.CoreService.GetReward:output_type -> core.v1.GetRewardResponse + 44, // 44: core.v1.CoreService.GetRewards:output_type -> core.v1.GetRewardsResponse + 45, // 45: core.v1.CoreService.GetRewardPool:output_type -> core.v1.GetRewardPoolResponse + 46, // 46: core.v1.CoreService.GetRewardAttestation:output_type -> core.v1.GetRewardAttestationResponse + 47, // 47: core.v1.CoreService.GetRewardSenderAttestation:output_type -> core.v1.GetRewardSenderAttestationResponse + 48, // 48: core.v1.CoreService.GetDeleteRewardSenderAttestation:output_type -> core.v1.GetDeleteRewardSenderAttestationResponse + 49, // 49: core.v1.CoreService.GetStreamURLs:output_type -> core.v1.GetStreamURLsResponse + 50, // 50: core.v1.CoreService.GetUploadByCID:output_type -> core.v1.GetUploadByCIDResponse + 51, // 51: core.v1.CoreService.StreamBlocks:output_type -> core.v1.StreamBlocksResponse + 26, // [26:52] is the sub-list for method output_type + 0, // [0:26] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/pkg/api/core/v1/types.pb.go b/pkg/api/core/v1/types.pb.go index d4b62283..8281ca5a 100644 --- a/pkg/api/core/v1/types.pb.go +++ b/pkg/api/core/v1/types.pb.go @@ -1500,6 +1500,7 @@ type SignedTransaction struct { // *SignedTransaction_Release // *SignedTransaction_Reward // *SignedTransaction_FileUpload + // *SignedTransaction_RewardPool Transaction isSignedTransaction_Transaction `protobuf_oneof:"transaction"` } @@ -1633,6 +1634,13 @@ func (x *SignedTransaction) GetFileUpload() *FileUpload { return nil } +func (x *SignedTransaction) GetRewardPool() *RewardPoolMessage { + if x, ok := x.GetTransaction().(*SignedTransaction_RewardPool); ok { + return x.RewardPool + } + return nil +} + type isSignedTransaction_Transaction interface { isSignedTransaction_Transaction() } @@ -1681,6 +1689,10 @@ type SignedTransaction_FileUpload struct { FileUpload *FileUpload `protobuf:"bytes,1010,opt,name=file_upload,json=fileUpload,proto3,oneof"` } +type SignedTransaction_RewardPool struct { + RewardPool *RewardPoolMessage `protobuf:"bytes,1011,opt,name=reward_pool,json=rewardPool,proto3,oneof"` +} + func (*SignedTransaction_Plays) isSignedTransaction_Transaction() {} func (*SignedTransaction_ValidatorRegistration) isSignedTransaction_Transaction() {} @@ -1703,6 +1715,8 @@ func (*SignedTransaction_Reward) isSignedTransaction_Transaction() {} func (*SignedTransaction_FileUpload) isSignedTransaction_Transaction() {} +func (*SignedTransaction_RewardPool) isSignedTransaction_Transaction() {} + type TrackPlays struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4096,16 +4110,17 @@ func (x *GetPIEResponse) GetPie() *v1beta11.PieMessage { return nil } +// RewardMessage is the wire envelope: it bundles a signed body with its +// signature. The body is what gets signed (deterministic protobuf bytes); +// the signature lives alongside the body, not inside it, so signing has no +// chicken-and-egg. type RewardMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Types that are assignable to Action: - // - // *RewardMessage_Create - // *RewardMessage_Delete - Action isRewardMessage_Action `protobuf_oneof:"action"` + Body *RewardBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` // signature over the deterministic marshaling of body } func (x *RewardMessage) Reset() { @@ -4140,60 +4155,134 @@ func (*RewardMessage) Descriptor() ([]byte, []int) { return file_core_v1_types_proto_rawDescGZIP(), []int{65} } -func (m *RewardMessage) GetAction() isRewardMessage_Action { +func (x *RewardMessage) GetBody() *RewardBody { + if x != nil { + return x.Body + } + return nil +} + +func (x *RewardMessage) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +// RewardBody is the signed payload. deadline_block_height bounds the +// signature's validity (the validator rejects after expiry); the action is +// the actual operation. The oneof tag discriminates Create from Delete so +// two actions with otherwise-identical inner shapes produce different bytes. +type RewardBody struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DeadlineBlockHeight int64 `protobuf:"varint,1,opt,name=deadline_block_height,json=deadlineBlockHeight,proto3" json:"deadline_block_height,omitempty"` + // Types that are assignable to Action: + // + // *RewardBody_Create + // *RewardBody_Delete + Action isRewardBody_Action `protobuf_oneof:"action"` +} + +func (x *RewardBody) Reset() { + *x = RewardBody{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RewardBody) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RewardBody) ProtoMessage() {} + +func (x *RewardBody) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[66] + 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 RewardBody.ProtoReflect.Descriptor instead. +func (*RewardBody) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{66} +} + +func (x *RewardBody) GetDeadlineBlockHeight() int64 { + if x != nil { + return x.DeadlineBlockHeight + } + return 0 +} + +func (m *RewardBody) GetAction() isRewardBody_Action { if m != nil { return m.Action } return nil } -func (x *RewardMessage) GetCreate() *CreateReward { - if x, ok := x.GetAction().(*RewardMessage_Create); ok { +func (x *RewardBody) GetCreate() *CreateReward { + if x, ok := x.GetAction().(*RewardBody_Create); ok { return x.Create } return nil } -func (x *RewardMessage) GetDelete() *DeleteReward { - if x, ok := x.GetAction().(*RewardMessage_Delete); ok { +func (x *RewardBody) GetDelete() *DeleteReward { + if x, ok := x.GetAction().(*RewardBody_Delete); ok { return x.Delete } return nil } -type isRewardMessage_Action interface { - isRewardMessage_Action() +type isRewardBody_Action interface { + isRewardBody_Action() } -type RewardMessage_Create struct { +type RewardBody_Create struct { Create *CreateReward `protobuf:"bytes,1000,opt,name=create,proto3,oneof"` } -type RewardMessage_Delete struct { +type RewardBody_Delete struct { Delete *DeleteReward `protobuf:"bytes,1001,opt,name=delete,proto3,oneof"` } -func (*RewardMessage_Create) isRewardMessage_Action() {} +func (*RewardBody_Create) isRewardBody_Action() {} -func (*RewardMessage_Delete) isRewardMessage_Action() {} +func (*RewardBody_Delete) isRewardBody_Action() {} type CreateReward struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RewardId string `protobuf:"bytes,1,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` - ClaimAuthorities []*ClaimAuthority `protobuf:"bytes,4,rep,name=claim_authorities,json=claimAuthorities,proto3" json:"claim_authorities,omitempty"` - DeadlineBlockHeight int64 `protobuf:"varint,5,opt,name=deadline_block_height,json=deadlineBlockHeight,proto3" json:"deadline_block_height,omitempty"` - Signature string `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` // Signature over deterministic reward data + RewardId string `protobuf:"bytes,1,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + // Solana reward manager pubkey (base58, 32 bytes). Identifies the pool + // whose authorities are allowed to issue rewards in this RM. The + // recovered signer must be a current member of pool.authorities; the + // reward inherits attestation rights from the pool going forward. + // The pool itself is identified by this same pubkey: pool address == + // rewards_manager_pubkey for first-class pools. + RewardsManagerPubkey string `protobuf:"bytes,7,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` } func (x *CreateReward) Reset() { *x = CreateReward{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[66] + mi := &file_core_v1_types_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4206,7 +4295,7 @@ func (x *CreateReward) String() string { func (*CreateReward) ProtoMessage() {} func (x *CreateReward) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[66] + mi := &file_core_v1_types_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4219,7 +4308,7 @@ func (x *CreateReward) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateReward.ProtoReflect.Descriptor instead. func (*CreateReward) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{66} + return file_core_v1_types_proto_rawDescGZIP(), []int{67} } func (x *CreateReward) GetRewardId() string { @@ -4243,23 +4332,9 @@ func (x *CreateReward) GetAmount() uint64 { return 0 } -func (x *CreateReward) GetClaimAuthorities() []*ClaimAuthority { - if x != nil { - return x.ClaimAuthorities - } - return nil -} - -func (x *CreateReward) GetDeadlineBlockHeight() int64 { - if x != nil { - return x.DeadlineBlockHeight - } - return 0 -} - -func (x *CreateReward) GetSignature() string { +func (x *CreateReward) GetRewardsManagerPubkey() string { if x != nil { - return x.Signature + return x.RewardsManagerPubkey } return "" } @@ -4269,15 +4344,13 @@ type DeleteReward struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // The deployed reward address - DeadlineBlockHeight int64 `protobuf:"varint,2,opt,name=deadline_block_height,json=deadlineBlockHeight,proto3" json:"deadline_block_height,omitempty"` - Signature string `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` // Signature over deterministic delete data + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // The deployed reward address } func (x *DeleteReward) Reset() { *x = DeleteReward{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[67] + mi := &file_core_v1_types_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4290,7 +4363,7 @@ func (x *DeleteReward) String() string { func (*DeleteReward) ProtoMessage() {} func (x *DeleteReward) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[67] + mi := &file_core_v1_types_proto_msgTypes[68] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4303,7 +4376,7 @@ func (x *DeleteReward) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteReward.ProtoReflect.Descriptor instead. func (*DeleteReward) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{67} + return file_core_v1_types_proto_rawDescGZIP(), []int{68} } func (x *DeleteReward) GetAddress() string { @@ -4313,46 +4386,34 @@ func (x *DeleteReward) GetAddress() string { return "" } -func (x *DeleteReward) GetDeadlineBlockHeight() int64 { - if x != nil { - return x.DeadlineBlockHeight - } - return 0 -} - -func (x *DeleteReward) GetSignature() string { - if x != nil { - return x.Signature - } - return "" -} - -type GetRewardRequest struct { +// RewardPoolMessage is the wire envelope for pool-management transactions. +// Same shape as RewardMessage: body + signature. +type RewardPoolMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // The deployed reward address - Txhash string `protobuf:"bytes,2,opt,name=txhash,proto3" json:"txhash,omitempty"` + Body *RewardPoolBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` } -func (x *GetRewardRequest) Reset() { - *x = GetRewardRequest{} +func (x *RewardPoolMessage) Reset() { + *x = RewardPoolMessage{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[68] + mi := &file_core_v1_types_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *GetRewardRequest) String() string { +func (x *RewardPoolMessage) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetRewardRequest) ProtoMessage() {} +func (*RewardPoolMessage) ProtoMessage() {} -func (x *GetRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[68] +func (x *RewardPoolMessage) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[69] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4363,56 +4424,55 @@ func (x *GetRewardRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetRewardRequest.ProtoReflect.Descriptor instead. -func (*GetRewardRequest) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{68} +// Deprecated: Use RewardPoolMessage.ProtoReflect.Descriptor instead. +func (*RewardPoolMessage) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{69} } -func (x *GetRewardRequest) GetAddress() string { +func (x *RewardPoolMessage) GetBody() *RewardPoolBody { if x != nil { - return x.Address + return x.Body } - return "" + return nil } -func (x *GetRewardRequest) GetTxhash() string { +func (x *RewardPoolMessage) GetSignature() string { if x != nil { - return x.Txhash + return x.Signature } return "" } -type GetRewardResponse struct { +type RewardPoolBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - RewardId string `protobuf:"bytes,2,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Amount uint64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"` - ClaimAuthorities []string `protobuf:"bytes,5,rep,name=claim_authorities,json=claimAuthorities,proto3" json:"claim_authorities,omitempty"` - Sender string `protobuf:"bytes,6,opt,name=sender,proto3" json:"sender,omitempty"` - BlockHeight int64 `protobuf:"varint,7,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + DeadlineBlockHeight int64 `protobuf:"varint,1,opt,name=deadline_block_height,json=deadlineBlockHeight,proto3" json:"deadline_block_height,omitempty"` + // Types that are assignable to Action: + // + // *RewardPoolBody_Create + // *RewardPoolBody_SetAuthorities + Action isRewardPoolBody_Action `protobuf_oneof:"action"` } -func (x *GetRewardResponse) Reset() { - *x = GetRewardResponse{} +func (x *RewardPoolBody) Reset() { + *x = RewardPoolBody{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[69] + mi := &file_core_v1_types_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *GetRewardResponse) String() string { +func (x *RewardPoolBody) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetRewardResponse) ProtoMessage() {} +func (*RewardPoolBody) ProtoMessage() {} -func (x *GetRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[69] +func (x *RewardPoolBody) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[70] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4423,90 +4483,89 @@ func (x *GetRewardResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetRewardResponse.ProtoReflect.Descriptor instead. -func (*GetRewardResponse) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{69} +// Deprecated: Use RewardPoolBody.ProtoReflect.Descriptor instead. +func (*RewardPoolBody) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{70} } -func (x *GetRewardResponse) GetAddress() string { +func (x *RewardPoolBody) GetDeadlineBlockHeight() int64 { if x != nil { - return x.Address + return x.DeadlineBlockHeight } - return "" + return 0 } -func (x *GetRewardResponse) GetRewardId() string { - if x != nil { - return x.RewardId +func (m *RewardPoolBody) GetAction() isRewardPoolBody_Action { + if m != nil { + return m.Action } - return "" + return nil } -func (x *GetRewardResponse) GetName() string { - if x != nil { - return x.Name +func (x *RewardPoolBody) GetCreate() *CreateRewardPool { + if x, ok := x.GetAction().(*RewardPoolBody_Create); ok { + return x.Create } - return "" + return nil } -func (x *GetRewardResponse) GetAmount() uint64 { - if x != nil { - return x.Amount +func (x *RewardPoolBody) GetSetAuthorities() *SetRewardPoolAuthorities { + if x, ok := x.GetAction().(*RewardPoolBody_SetAuthorities); ok { + return x.SetAuthorities } - return 0 + return nil } -func (x *GetRewardResponse) GetClaimAuthorities() []string { - if x != nil { - return x.ClaimAuthorities - } - return nil +type isRewardPoolBody_Action interface { + isRewardPoolBody_Action() } -func (x *GetRewardResponse) GetSender() string { - if x != nil { - return x.Sender - } - return "" +type RewardPoolBody_Create struct { + Create *CreateRewardPool `protobuf:"bytes,2000,opt,name=create,proto3,oneof"` } -func (x *GetRewardResponse) GetBlockHeight() int64 { - if x != nil { - return x.BlockHeight - } - return 0 +type RewardPoolBody_SetAuthorities struct { + SetAuthorities *SetRewardPoolAuthorities `protobuf:"bytes,2001,opt,name=set_authorities,json=setAuthorities,proto3,oneof"` } -type RewardAttestationSignature struct { +func (*RewardPoolBody_Create) isRewardPoolBody_Action() {} + +func (*RewardPoolBody_SetAuthorities) isRewardPoolBody_Action() {} + +type CreateRewardPool struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - EthRecipientAddress string `protobuf:"bytes,1,opt,name=eth_recipient_address,json=ethRecipientAddress,proto3" json:"eth_recipient_address,omitempty"` - Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` - RewardAddress string `protobuf:"bytes,3,opt,name=reward_address,json=rewardAddress,proto3" json:"reward_address,omitempty"` // Binds signature to specific deployed reward - RewardId string `protobuf:"bytes,4,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` - Specifier string `protobuf:"bytes,5,opt,name=specifier,proto3" json:"specifier,omitempty"` - ClaimAuthority string `protobuf:"bytes,6,opt,name=claim_authority,json=claimAuthority,proto3" json:"claim_authority,omitempty"` + // The pool is identified by its Solana reward manager pubkey (base58, + // 32 bytes). Subsequent CreateReward / SetRewardPoolAuthorities messages + // and PR3's sender-attestation gate use this same value to resolve the + // pool. There is no separate "pool address" — the pool's identity IS the + // RM pubkey, so pool↔RM binding cannot be set wrong by construction. + RewardsManagerPubkey string `protobuf:"bytes,1,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` + // Initial set of eth addresses authorized to attest for rewards in this + // pool. The recovered signer must be a member of this list. Each entry + // must be a valid eth hex address. + Authorities []string `protobuf:"bytes,2,rep,name=authorities,proto3" json:"authorities,omitempty"` } -func (x *RewardAttestationSignature) Reset() { - *x = RewardAttestationSignature{} +func (x *CreateRewardPool) Reset() { + *x = CreateRewardPool{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[70] + mi := &file_core_v1_types_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *RewardAttestationSignature) String() string { +func (x *CreateRewardPool) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RewardAttestationSignature) ProtoMessage() {} +func (*CreateRewardPool) ProtoMessage() {} -func (x *RewardAttestationSignature) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[70] +func (x *CreateRewardPool) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4517,78 +4576,691 @@ func (x *RewardAttestationSignature) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RewardAttestationSignature.ProtoReflect.Descriptor instead. -func (*RewardAttestationSignature) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{70} -} - -func (x *RewardAttestationSignature) GetEthRecipientAddress() string { - if x != nil { - return x.EthRecipientAddress - } - return "" -} - -func (x *RewardAttestationSignature) GetAmount() uint64 { - if x != nil { - return x.Amount - } - return 0 -} - -func (x *RewardAttestationSignature) GetRewardAddress() string { - if x != nil { - return x.RewardAddress - } - return "" -} - -func (x *RewardAttestationSignature) GetRewardId() string { - if x != nil { - return x.RewardId - } - return "" +// Deprecated: Use CreateRewardPool.ProtoReflect.Descriptor instead. +func (*CreateRewardPool) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{71} } -func (x *RewardAttestationSignature) GetSpecifier() string { +func (x *CreateRewardPool) GetRewardsManagerPubkey() string { if x != nil { - return x.Specifier + return x.RewardsManagerPubkey } return "" } -func (x *RewardAttestationSignature) GetClaimAuthority() string { +func (x *CreateRewardPool) GetAuthorities() []string { if x != nil { - return x.ClaimAuthority + return x.Authorities } - return "" + return nil } -type UploadSignature struct { +// SetRewardPoolAuthorities replaces the pool's authority set wholesale. The +// signer must be in the *current* pool.authorities; the new list must be +// non-empty and contain only valid eth addresses (otherwise the pool is +// permanently orphaned). Add and remove are derived views: callers compose +// the new list themselves. +type SetRewardPoolAuthorities struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Cid string `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"` + RewardsManagerPubkey string `protobuf:"bytes,1,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` + Authorities []string `protobuf:"bytes,2,rep,name=authorities,proto3" json:"authorities,omitempty"` } -func (x *UploadSignature) Reset() { - *x = UploadSignature{} +func (x *SetRewardPoolAuthorities) Reset() { + *x = SetRewardPoolAuthorities{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[71] + mi := &file_core_v1_types_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *UploadSignature) String() string { +func (x *SetRewardPoolAuthorities) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UploadSignature) ProtoMessage() {} +func (*SetRewardPoolAuthorities) ProtoMessage() {} -func (x *UploadSignature) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[71] +func (x *SetRewardPoolAuthorities) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[72] + 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 SetRewardPoolAuthorities.ProtoReflect.Descriptor instead. +func (*SetRewardPoolAuthorities) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{72} +} + +func (x *SetRewardPoolAuthorities) GetRewardsManagerPubkey() string { + if x != nil { + return x.RewardsManagerPubkey + } + return "" +} + +func (x *SetRewardPoolAuthorities) GetAuthorities() []string { + if x != nil { + return x.Authorities + } + return nil +} + +// === Legacy reward wire format (pre-pool-rollout) === +// +// These messages are the on-the-wire shape used before the body+signature +// envelope was introduced. They are NOT used by new clients or new code paths +// — they exist solely so that the new binary can decode and apply historical +// reward transactions encountered during block-sync-from-genesis. +// +// DO NOT REMOVE these types or their field tags: removing them would break +// replay of any chain history that contains rewards committed before the +// upgrade. Adding new fields here is also forbidden — the wire shape must +// remain pinned to what the old network produced. +type LegacyRewardMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Action: + // + // *LegacyRewardMessage_Create + // *LegacyRewardMessage_Delete + Action isLegacyRewardMessage_Action `protobuf_oneof:"action"` +} + +func (x *LegacyRewardMessage) Reset() { + *x = LegacyRewardMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LegacyRewardMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LegacyRewardMessage) ProtoMessage() {} + +func (x *LegacyRewardMessage) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[73] + 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 LegacyRewardMessage.ProtoReflect.Descriptor instead. +func (*LegacyRewardMessage) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{73} +} + +func (m *LegacyRewardMessage) GetAction() isLegacyRewardMessage_Action { + if m != nil { + return m.Action + } + return nil +} + +func (x *LegacyRewardMessage) GetCreate() *LegacyCreateReward { + if x, ok := x.GetAction().(*LegacyRewardMessage_Create); ok { + return x.Create + } + return nil +} + +func (x *LegacyRewardMessage) GetDelete() *LegacyDeleteReward { + if x, ok := x.GetAction().(*LegacyRewardMessage_Delete); ok { + return x.Delete + } + return nil +} + +type isLegacyRewardMessage_Action interface { + isLegacyRewardMessage_Action() +} + +type LegacyRewardMessage_Create struct { + Create *LegacyCreateReward `protobuf:"bytes,1000,opt,name=create,proto3,oneof"` +} + +type LegacyRewardMessage_Delete struct { + Delete *LegacyDeleteReward `protobuf:"bytes,1001,opt,name=delete,proto3,oneof"` +} + +func (*LegacyRewardMessage_Create) isLegacyRewardMessage_Action() {} + +func (*LegacyRewardMessage_Delete) isLegacyRewardMessage_Action() {} + +type LegacyCreateReward struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RewardId string `protobuf:"bytes,1,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + ClaimAuthorities []*ClaimAuthority `protobuf:"bytes,4,rep,name=claim_authorities,json=claimAuthorities,proto3" json:"claim_authorities,omitempty"` + DeadlineBlockHeight int64 `protobuf:"varint,5,opt,name=deadline_block_height,json=deadlineBlockHeight,proto3" json:"deadline_block_height,omitempty"` + Signature string `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *LegacyCreateReward) Reset() { + *x = LegacyCreateReward{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LegacyCreateReward) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LegacyCreateReward) ProtoMessage() {} + +func (x *LegacyCreateReward) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[74] + 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 LegacyCreateReward.ProtoReflect.Descriptor instead. +func (*LegacyCreateReward) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{74} +} + +func (x *LegacyCreateReward) GetRewardId() string { + if x != nil { + return x.RewardId + } + return "" +} + +func (x *LegacyCreateReward) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *LegacyCreateReward) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *LegacyCreateReward) GetClaimAuthorities() []*ClaimAuthority { + if x != nil { + return x.ClaimAuthorities + } + return nil +} + +func (x *LegacyCreateReward) GetDeadlineBlockHeight() int64 { + if x != nil { + return x.DeadlineBlockHeight + } + return 0 +} + +func (x *LegacyCreateReward) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +type LegacyDeleteReward struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + DeadlineBlockHeight int64 `protobuf:"varint,2,opt,name=deadline_block_height,json=deadlineBlockHeight,proto3" json:"deadline_block_height,omitempty"` + Signature string `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *LegacyDeleteReward) Reset() { + *x = LegacyDeleteReward{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LegacyDeleteReward) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LegacyDeleteReward) ProtoMessage() {} + +func (x *LegacyDeleteReward) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[75] + 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 LegacyDeleteReward.ProtoReflect.Descriptor instead. +func (*LegacyDeleteReward) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{75} +} + +func (x *LegacyDeleteReward) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *LegacyDeleteReward) GetDeadlineBlockHeight() int64 { + if x != nil { + return x.DeadlineBlockHeight + } + return 0 +} + +func (x *LegacyDeleteReward) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +type GetRewardPoolRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RewardsManagerPubkey string `protobuf:"bytes,1,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` +} + +func (x *GetRewardPoolRequest) Reset() { + *x = GetRewardPoolRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRewardPoolRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRewardPoolRequest) ProtoMessage() {} + +func (x *GetRewardPoolRequest) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[76] + 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 GetRewardPoolRequest.ProtoReflect.Descriptor instead. +func (*GetRewardPoolRequest) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{76} +} + +func (x *GetRewardPoolRequest) GetRewardsManagerPubkey() string { + if x != nil { + return x.RewardsManagerPubkey + } + return "" +} + +type GetRewardPoolResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RewardsManagerPubkey string `protobuf:"bytes,1,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` + Authorities []string `protobuf:"bytes,2,rep,name=authorities,proto3" json:"authorities,omitempty"` +} + +func (x *GetRewardPoolResponse) Reset() { + *x = GetRewardPoolResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRewardPoolResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRewardPoolResponse) ProtoMessage() {} + +func (x *GetRewardPoolResponse) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[77] + 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 GetRewardPoolResponse.ProtoReflect.Descriptor instead. +func (*GetRewardPoolResponse) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{77} +} + +func (x *GetRewardPoolResponse) GetRewardsManagerPubkey() string { + if x != nil { + return x.RewardsManagerPubkey + } + return "" +} + +func (x *GetRewardPoolResponse) GetAuthorities() []string { + if x != nil { + return x.Authorities + } + return nil +} + +type GetRewardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // The deployed reward address + Txhash string `protobuf:"bytes,2,opt,name=txhash,proto3" json:"txhash,omitempty"` +} + +func (x *GetRewardRequest) Reset() { + *x = GetRewardRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[78] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRewardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRewardRequest) ProtoMessage() {} + +func (x *GetRewardRequest) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[78] + 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 GetRewardRequest.ProtoReflect.Descriptor instead. +func (*GetRewardRequest) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{78} +} + +func (x *GetRewardRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *GetRewardRequest) GetTxhash() string { + if x != nil { + return x.Txhash + } + return "" +} + +type GetRewardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + RewardId string `protobuf:"bytes,2,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Amount uint64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"` + ClaimAuthorities []string `protobuf:"bytes,5,rep,name=claim_authorities,json=claimAuthorities,proto3" json:"claim_authorities,omitempty"` + Sender string `protobuf:"bytes,6,opt,name=sender,proto3" json:"sender,omitempty"` + BlockHeight int64 `protobuf:"varint,7,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` +} + +func (x *GetRewardResponse) Reset() { + *x = GetRewardResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[79] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRewardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRewardResponse) ProtoMessage() {} + +func (x *GetRewardResponse) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[79] + 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 GetRewardResponse.ProtoReflect.Descriptor instead. +func (*GetRewardResponse) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{79} +} + +func (x *GetRewardResponse) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *GetRewardResponse) GetRewardId() string { + if x != nil { + return x.RewardId + } + return "" +} + +func (x *GetRewardResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetRewardResponse) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *GetRewardResponse) GetClaimAuthorities() []string { + if x != nil { + return x.ClaimAuthorities + } + return nil +} + +func (x *GetRewardResponse) GetSender() string { + if x != nil { + return x.Sender + } + return "" +} + +func (x *GetRewardResponse) GetBlockHeight() int64 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +type RewardAttestationSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EthRecipientAddress string `protobuf:"bytes,1,opt,name=eth_recipient_address,json=ethRecipientAddress,proto3" json:"eth_recipient_address,omitempty"` + Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + RewardAddress string `protobuf:"bytes,3,opt,name=reward_address,json=rewardAddress,proto3" json:"reward_address,omitempty"` // Binds signature to specific deployed reward + RewardId string `protobuf:"bytes,4,opt,name=reward_id,json=rewardId,proto3" json:"reward_id,omitempty"` + Specifier string `protobuf:"bytes,5,opt,name=specifier,proto3" json:"specifier,omitempty"` + ClaimAuthority string `protobuf:"bytes,6,opt,name=claim_authority,json=claimAuthority,proto3" json:"claim_authority,omitempty"` +} + +func (x *RewardAttestationSignature) Reset() { + *x = RewardAttestationSignature{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[80] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RewardAttestationSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RewardAttestationSignature) ProtoMessage() {} + +func (x *RewardAttestationSignature) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[80] + 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 RewardAttestationSignature.ProtoReflect.Descriptor instead. +func (*RewardAttestationSignature) Descriptor() ([]byte, []int) { + return file_core_v1_types_proto_rawDescGZIP(), []int{80} +} + +func (x *RewardAttestationSignature) GetEthRecipientAddress() string { + if x != nil { + return x.EthRecipientAddress + } + return "" +} + +func (x *RewardAttestationSignature) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *RewardAttestationSignature) GetRewardAddress() string { + if x != nil { + return x.RewardAddress + } + return "" +} + +func (x *RewardAttestationSignature) GetRewardId() string { + if x != nil { + return x.RewardId + } + return "" +} + +func (x *RewardAttestationSignature) GetSpecifier() string { + if x != nil { + return x.Specifier + } + return "" +} + +func (x *RewardAttestationSignature) GetClaimAuthority() string { + if x != nil { + return x.ClaimAuthority + } + return "" +} + +type UploadSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Cid string `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"` +} + +func (x *UploadSignature) Reset() { + *x = UploadSignature{} + if protoimpl.UnsafeEnabled { + mi := &file_core_v1_types_proto_msgTypes[81] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UploadSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UploadSignature) ProtoMessage() {} + +func (x *UploadSignature) ProtoReflect() protoreflect.Message { + mi := &file_core_v1_types_proto_msgTypes[81] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4601,7 +5273,7 @@ func (x *UploadSignature) ProtoReflect() protoreflect.Message { // Deprecated: Use UploadSignature.ProtoReflect.Descriptor instead. func (*UploadSignature) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{71} + return file_core_v1_types_proto_rawDescGZIP(), []int{81} } func (x *UploadSignature) GetCid() string { @@ -4637,7 +5309,7 @@ type FileUpload struct { func (x *FileUpload) Reset() { *x = FileUpload{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[72] + mi := &file_core_v1_types_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4650,7 +5322,7 @@ func (x *FileUpload) String() string { func (*FileUpload) ProtoMessage() {} func (x *FileUpload) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[72] + mi := &file_core_v1_types_proto_msgTypes[82] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4663,7 +5335,7 @@ func (x *FileUpload) ProtoReflect() protoreflect.Message { // Deprecated: Use FileUpload.ProtoReflect.Descriptor instead. func (*FileUpload) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{72} + return file_core_v1_types_proto_rawDescGZIP(), []int{82} } func (x *FileUpload) GetUploaderAddress() string { @@ -4728,7 +5400,7 @@ type GetStreamURLsSignature struct { func (x *GetStreamURLsSignature) Reset() { *x = GetStreamURLsSignature{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[73] + mi := &file_core_v1_types_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4741,7 +5413,7 @@ func (x *GetStreamURLsSignature) String() string { func (*GetStreamURLsSignature) ProtoMessage() {} func (x *GetStreamURLsSignature) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[73] + mi := &file_core_v1_types_proto_msgTypes[83] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4754,7 +5426,7 @@ func (x *GetStreamURLsSignature) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStreamURLsSignature.ProtoReflect.Descriptor instead. func (*GetStreamURLsSignature) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{73} + return file_core_v1_types_proto_rawDescGZIP(), []int{83} } func (x *GetStreamURLsSignature) GetAddresses() []string { @@ -4791,7 +5463,7 @@ type GetStreamURLsRequest struct { func (x *GetStreamURLsRequest) Reset() { *x = GetStreamURLsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[74] + mi := &file_core_v1_types_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4804,7 +5476,7 @@ func (x *GetStreamURLsRequest) String() string { func (*GetStreamURLsRequest) ProtoMessage() {} func (x *GetStreamURLsRequest) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[74] + mi := &file_core_v1_types_proto_msgTypes[84] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4817,7 +5489,7 @@ func (x *GetStreamURLsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStreamURLsRequest.ProtoReflect.Descriptor instead. func (*GetStreamURLsRequest) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{74} + return file_core_v1_types_proto_rawDescGZIP(), []int{84} } func (x *GetStreamURLsRequest) GetSignature() string { @@ -4854,7 +5526,7 @@ type GetStreamURLsResponse struct { func (x *GetStreamURLsResponse) Reset() { *x = GetStreamURLsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[75] + mi := &file_core_v1_types_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4867,7 +5539,7 @@ func (x *GetStreamURLsResponse) String() string { func (*GetStreamURLsResponse) ProtoMessage() {} func (x *GetStreamURLsResponse) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[75] + mi := &file_core_v1_types_proto_msgTypes[85] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4880,7 +5552,7 @@ func (x *GetStreamURLsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStreamURLsResponse.ProtoReflect.Descriptor instead. func (*GetStreamURLsResponse) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{75} + return file_core_v1_types_proto_rawDescGZIP(), []int{85} } func (x *GetStreamURLsResponse) GetEntityStreamUrls() map[string]*GetStreamURLsResponse_EntityStreamURLs { @@ -4901,7 +5573,7 @@ type GetUploadByCIDRequest struct { func (x *GetUploadByCIDRequest) Reset() { *x = GetUploadByCIDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[76] + mi := &file_core_v1_types_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4914,7 +5586,7 @@ func (x *GetUploadByCIDRequest) String() string { func (*GetUploadByCIDRequest) ProtoMessage() {} func (x *GetUploadByCIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[76] + mi := &file_core_v1_types_proto_msgTypes[86] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4927,7 +5599,7 @@ func (x *GetUploadByCIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUploadByCIDRequest.ProtoReflect.Descriptor instead. func (*GetUploadByCIDRequest) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{76} + return file_core_v1_types_proto_rawDescGZIP(), []int{86} } func (x *GetUploadByCIDRequest) GetCid() string { @@ -4951,7 +5623,7 @@ type GetUploadByCIDResponse struct { func (x *GetUploadByCIDResponse) Reset() { *x = GetUploadByCIDResponse{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[77] + mi := &file_core_v1_types_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4964,7 +5636,7 @@ func (x *GetUploadByCIDResponse) String() string { func (*GetUploadByCIDResponse) ProtoMessage() {} func (x *GetUploadByCIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[77] + mi := &file_core_v1_types_proto_msgTypes[87] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4977,7 +5649,7 @@ func (x *GetUploadByCIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUploadByCIDResponse.ProtoReflect.Descriptor instead. func (*GetUploadByCIDResponse) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{77} + return file_core_v1_types_proto_rawDescGZIP(), []int{87} } func (x *GetUploadByCIDResponse) GetExists() bool { @@ -5020,7 +5692,7 @@ type GetRewardSenderAttestationRequest struct { func (x *GetRewardSenderAttestationRequest) Reset() { *x = GetRewardSenderAttestationRequest{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[78] + mi := &file_core_v1_types_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5033,7 +5705,7 @@ func (x *GetRewardSenderAttestationRequest) String() string { func (*GetRewardSenderAttestationRequest) ProtoMessage() {} func (x *GetRewardSenderAttestationRequest) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[78] + mi := &file_core_v1_types_proto_msgTypes[88] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5046,7 +5718,7 @@ func (x *GetRewardSenderAttestationRequest) ProtoReflect() protoreflect.Message // Deprecated: Use GetRewardSenderAttestationRequest.ProtoReflect.Descriptor instead. func (*GetRewardSenderAttestationRequest) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{78} + return file_core_v1_types_proto_rawDescGZIP(), []int{88} } func (x *GetRewardSenderAttestationRequest) GetAddress() string { @@ -5075,7 +5747,7 @@ type GetRewardSenderAttestationResponse struct { func (x *GetRewardSenderAttestationResponse) Reset() { *x = GetRewardSenderAttestationResponse{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[79] + mi := &file_core_v1_types_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5088,7 +5760,7 @@ func (x *GetRewardSenderAttestationResponse) String() string { func (*GetRewardSenderAttestationResponse) ProtoMessage() {} func (x *GetRewardSenderAttestationResponse) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[79] + mi := &file_core_v1_types_proto_msgTypes[89] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5101,7 +5773,7 @@ func (x *GetRewardSenderAttestationResponse) ProtoReflect() protoreflect.Message // Deprecated: Use GetRewardSenderAttestationResponse.ProtoReflect.Descriptor instead. func (*GetRewardSenderAttestationResponse) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{79} + return file_core_v1_types_proto_rawDescGZIP(), []int{89} } func (x *GetRewardSenderAttestationResponse) GetOwner() string { @@ -5130,7 +5802,7 @@ type GetDeleteRewardSenderAttestationRequest struct { func (x *GetDeleteRewardSenderAttestationRequest) Reset() { *x = GetDeleteRewardSenderAttestationRequest{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[80] + mi := &file_core_v1_types_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5143,7 +5815,7 @@ func (x *GetDeleteRewardSenderAttestationRequest) String() string { func (*GetDeleteRewardSenderAttestationRequest) ProtoMessage() {} func (x *GetDeleteRewardSenderAttestationRequest) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[80] + mi := &file_core_v1_types_proto_msgTypes[90] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5156,7 +5828,7 @@ func (x *GetDeleteRewardSenderAttestationRequest) ProtoReflect() protoreflect.Me // Deprecated: Use GetDeleteRewardSenderAttestationRequest.ProtoReflect.Descriptor instead. func (*GetDeleteRewardSenderAttestationRequest) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{80} + return file_core_v1_types_proto_rawDescGZIP(), []int{90} } func (x *GetDeleteRewardSenderAttestationRequest) GetAddress() string { @@ -5185,7 +5857,7 @@ type GetDeleteRewardSenderAttestationResponse struct { func (x *GetDeleteRewardSenderAttestationResponse) Reset() { *x = GetDeleteRewardSenderAttestationResponse{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[81] + mi := &file_core_v1_types_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5198,7 +5870,7 @@ func (x *GetDeleteRewardSenderAttestationResponse) String() string { func (*GetDeleteRewardSenderAttestationResponse) ProtoMessage() {} func (x *GetDeleteRewardSenderAttestationResponse) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[81] + mi := &file_core_v1_types_proto_msgTypes[91] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5211,7 +5883,7 @@ func (x *GetDeleteRewardSenderAttestationResponse) ProtoReflect() protoreflect.M // Deprecated: Use GetDeleteRewardSenderAttestationResponse.ProtoReflect.Descriptor instead. func (*GetDeleteRewardSenderAttestationResponse) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{81} + return file_core_v1_types_proto_rawDescGZIP(), []int{91} } func (x *GetDeleteRewardSenderAttestationResponse) GetOwner() string { @@ -5239,7 +5911,7 @@ type StreamBlocksRequest struct { func (x *StreamBlocksRequest) Reset() { *x = StreamBlocksRequest{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[82] + mi := &file_core_v1_types_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5252,7 +5924,7 @@ func (x *StreamBlocksRequest) String() string { func (*StreamBlocksRequest) ProtoMessage() {} func (x *StreamBlocksRequest) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[82] + mi := &file_core_v1_types_proto_msgTypes[92] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5265,7 +5937,7 @@ func (x *StreamBlocksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamBlocksRequest.ProtoReflect.Descriptor instead. func (*StreamBlocksRequest) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{82} + return file_core_v1_types_proto_rawDescGZIP(), []int{92} } func (x *StreamBlocksRequest) GetCanon() bool { @@ -5286,7 +5958,7 @@ type StreamBlocksResponse struct { func (x *StreamBlocksResponse) Reset() { *x = StreamBlocksResponse{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[83] + mi := &file_core_v1_types_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5299,7 +5971,7 @@ func (x *StreamBlocksResponse) String() string { func (*StreamBlocksResponse) ProtoMessage() {} func (x *StreamBlocksResponse) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[83] + mi := &file_core_v1_types_proto_msgTypes[93] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5312,7 +5984,7 @@ func (x *StreamBlocksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamBlocksResponse.ProtoReflect.Descriptor instead. func (*StreamBlocksResponse) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{83} + return file_core_v1_types_proto_rawDescGZIP(), []int{93} } func (x *StreamBlocksResponse) GetBlock() *Block { @@ -5342,7 +6014,7 @@ type GetStatusResponse_ProcessInfo struct { func (x *GetStatusResponse_ProcessInfo) Reset() { *x = GetStatusResponse_ProcessInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[84] + mi := &file_core_v1_types_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5355,7 +6027,7 @@ func (x *GetStatusResponse_ProcessInfo) String() string { func (*GetStatusResponse_ProcessInfo) ProtoMessage() {} func (x *GetStatusResponse_ProcessInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[84] + mi := &file_core_v1_types_proto_msgTypes[94] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5455,7 +6127,7 @@ type GetStatusResponse_NodeInfo struct { func (x *GetStatusResponse_NodeInfo) Reset() { *x = GetStatusResponse_NodeInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[85] + mi := &file_core_v1_types_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5468,7 +6140,7 @@ func (x *GetStatusResponse_NodeInfo) String() string { func (*GetStatusResponse_NodeInfo) ProtoMessage() {} func (x *GetStatusResponse_NodeInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[85] + mi := &file_core_v1_types_proto_msgTypes[95] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5526,7 +6198,7 @@ type GetStatusResponse_ChainInfo struct { func (x *GetStatusResponse_ChainInfo) Reset() { *x = GetStatusResponse_ChainInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[86] + mi := &file_core_v1_types_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5539,7 +6211,7 @@ func (x *GetStatusResponse_ChainInfo) String() string { func (*GetStatusResponse_ChainInfo) ProtoMessage() {} func (x *GetStatusResponse_ChainInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[86] + mi := &file_core_v1_types_proto_msgTypes[96] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5599,7 +6271,7 @@ type GetStatusResponse_SyncInfo struct { func (x *GetStatusResponse_SyncInfo) Reset() { *x = GetStatusResponse_SyncInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[87] + mi := &file_core_v1_types_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5612,7 +6284,7 @@ func (x *GetStatusResponse_SyncInfo) String() string { func (*GetStatusResponse_SyncInfo) ProtoMessage() {} func (x *GetStatusResponse_SyncInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[87] + mi := &file_core_v1_types_proto_msgTypes[97] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5690,7 +6362,7 @@ type GetStatusResponse_PruningInfo struct { func (x *GetStatusResponse_PruningInfo) Reset() { *x = GetStatusResponse_PruningInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[88] + mi := &file_core_v1_types_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5703,7 +6375,7 @@ func (x *GetStatusResponse_PruningInfo) String() string { func (*GetStatusResponse_PruningInfo) ProtoMessage() {} func (x *GetStatusResponse_PruningInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[88] + mi := &file_core_v1_types_proto_msgTypes[98] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5792,7 +6464,7 @@ type GetStatusResponse_ResourceInfo struct { func (x *GetStatusResponse_ResourceInfo) Reset() { *x = GetStatusResponse_ResourceInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[89] + mi := &file_core_v1_types_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5805,7 +6477,7 @@ func (x *GetStatusResponse_ResourceInfo) String() string { func (*GetStatusResponse_ResourceInfo) ProtoMessage() {} func (x *GetStatusResponse_ResourceInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[89] + mi := &file_core_v1_types_proto_msgTypes[99] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5884,7 +6556,7 @@ type GetStatusResponse_MempoolInfo struct { func (x *GetStatusResponse_MempoolInfo) Reset() { *x = GetStatusResponse_MempoolInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[90] + mi := &file_core_v1_types_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5897,7 +6569,7 @@ func (x *GetStatusResponse_MempoolInfo) String() string { func (*GetStatusResponse_MempoolInfo) ProtoMessage() {} func (x *GetStatusResponse_MempoolInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[90] + mi := &file_core_v1_types_proto_msgTypes[100] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5953,7 +6625,7 @@ type GetStatusResponse_SnapshotInfo struct { func (x *GetStatusResponse_SnapshotInfo) Reset() { *x = GetStatusResponse_SnapshotInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[91] + mi := &file_core_v1_types_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5966,7 +6638,7 @@ func (x *GetStatusResponse_SnapshotInfo) String() string { func (*GetStatusResponse_SnapshotInfo) ProtoMessage() {} func (x *GetStatusResponse_SnapshotInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[91] + mi := &file_core_v1_types_proto_msgTypes[101] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6007,7 +6679,7 @@ type GetStatusResponse_PeerInfo struct { func (x *GetStatusResponse_PeerInfo) Reset() { *x = GetStatusResponse_PeerInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[92] + mi := &file_core_v1_types_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6020,7 +6692,7 @@ func (x *GetStatusResponse_PeerInfo) String() string { func (*GetStatusResponse_PeerInfo) ProtoMessage() {} func (x *GetStatusResponse_PeerInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[92] + mi := &file_core_v1_types_proto_msgTypes[102] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6059,7 +6731,7 @@ type GetStatusResponse_StorageInfo struct { func (x *GetStatusResponse_StorageInfo) Reset() { *x = GetStatusResponse_StorageInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[93] + mi := &file_core_v1_types_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6072,7 +6744,7 @@ func (x *GetStatusResponse_StorageInfo) String() string { func (*GetStatusResponse_StorageInfo) ProtoMessage() {} func (x *GetStatusResponse_StorageInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[93] + mi := &file_core_v1_types_proto_msgTypes[103] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6145,7 +6817,7 @@ type GetStatusResponse_ProcessInfo_ProcessStateInfo struct { func (x *GetStatusResponse_ProcessInfo_ProcessStateInfo) Reset() { *x = GetStatusResponse_ProcessInfo_ProcessStateInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[94] + mi := &file_core_v1_types_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6158,7 +6830,7 @@ func (x *GetStatusResponse_ProcessInfo_ProcessStateInfo) String() string { func (*GetStatusResponse_ProcessInfo_ProcessStateInfo) ProtoMessage() {} func (x *GetStatusResponse_ProcessInfo_ProcessStateInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[94] + mi := &file_core_v1_types_proto_msgTypes[104] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6225,7 +6897,7 @@ type GetStatusResponse_SyncInfo_StateSyncInfo struct { func (x *GetStatusResponse_SyncInfo_StateSyncInfo) Reset() { *x = GetStatusResponse_SyncInfo_StateSyncInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[95] + mi := &file_core_v1_types_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6238,7 +6910,7 @@ func (x *GetStatusResponse_SyncInfo_StateSyncInfo) String() string { func (*GetStatusResponse_SyncInfo_StateSyncInfo) ProtoMessage() {} func (x *GetStatusResponse_SyncInfo_StateSyncInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[95] + mi := &file_core_v1_types_proto_msgTypes[105] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6312,7 +6984,7 @@ type GetStatusResponse_SyncInfo_BlockSyncInfo struct { func (x *GetStatusResponse_SyncInfo_BlockSyncInfo) Reset() { *x = GetStatusResponse_SyncInfo_BlockSyncInfo{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[96] + mi := &file_core_v1_types_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6325,7 +6997,7 @@ func (x *GetStatusResponse_SyncInfo_BlockSyncInfo) String() string { func (*GetStatusResponse_SyncInfo_BlockSyncInfo) ProtoMessage() {} func (x *GetStatusResponse_SyncInfo_BlockSyncInfo) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[96] + mi := &file_core_v1_types_proto_msgTypes[106] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6404,7 +7076,7 @@ type GetStatusResponse_PeerInfo_Peer struct { func (x *GetStatusResponse_PeerInfo_Peer) Reset() { *x = GetStatusResponse_PeerInfo_Peer{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[97] + mi := &file_core_v1_types_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6417,7 +7089,7 @@ func (x *GetStatusResponse_PeerInfo_Peer) String() string { func (*GetStatusResponse_PeerInfo_Peer) ProtoMessage() {} func (x *GetStatusResponse_PeerInfo_Peer) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[97] + mi := &file_core_v1_types_proto_msgTypes[107] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6528,7 +7200,7 @@ type GetStreamURLsResponse_EntityStreamURLs struct { func (x *GetStreamURLsResponse_EntityStreamURLs) Reset() { *x = GetStreamURLsResponse_EntityStreamURLs{} if protoimpl.UnsafeEnabled { - mi := &file_core_v1_types_proto_msgTypes[99] + mi := &file_core_v1_types_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6541,7 +7213,7 @@ func (x *GetStreamURLsResponse_EntityStreamURLs) String() string { func (*GetStreamURLsResponse_EntityStreamURLs) ProtoMessage() {} func (x *GetStreamURLsResponse_EntityStreamURLs) ProtoReflect() protoreflect.Message { - mi := &file_core_v1_types_proto_msgTypes[99] + mi := &file_core_v1_types_proto_msgTypes[109] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6554,7 +7226,7 @@ func (x *GetStreamURLsResponse_EntityStreamURLs) ProtoReflect() protoreflect.Mes // Deprecated: Use GetStreamURLsResponse_EntityStreamURLs.ProtoReflect.Descriptor instead. func (*GetStreamURLsResponse_EntityStreamURLs) Descriptor() ([]byte, []int) { - return file_core_v1_types_proto_rawDescGZIP(), []int{75, 0} + return file_core_v1_types_proto_rawDescGZIP(), []int{85, 0} } func (x *GetStreamURLsResponse_EntityStreamURLs) GetEntityType() string { @@ -7069,7 +7741,7 @@ var file_core_v1_types_proto_rawDesc = []byte{ 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x32, 0x22, 0xdb, 0x06, 0x0a, 0x11, 0x53, 0x69, 0x67, 0x6e, 0x65, + 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x32, 0x22, 0x9b, 0x07, 0x0a, 0x11, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, @@ -7122,7 +7794,11 @@ var file_core_v1_types_proto_rawDesc = []byte{ 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0xf2, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, + 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0xf3, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x36, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x63, @@ -7397,183 +8073,265 @@ var file_core_v1_types_proto_rawDesc = []byte{ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x03, 0x70, 0x69, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x64, 0x65, 0x78, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x69, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x03, 0x70, 0x69, - 0x65, 0x22, 0x7d, 0x0a, 0x0d, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0xe8, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0xe9, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0xef, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x44, 0x0a, 0x11, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x10, - 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x22, 0x7a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x15, + 0x65, 0x22, 0x56, 0x0a, 0x0d, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xae, 0x01, 0x0a, 0x0a, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, + 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x30, 0x0a, 0x06, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x30, + 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xd4, 0x01, 0x0a, 0x0c, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, + 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x52, 0x11, 0x63, 0x6c, + 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, + 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x22, 0x56, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, + 0x03, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, + 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, + 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, + 0x6c, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x44, - 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x74, 0x78, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, - 0x68, 0x61, 0x73, 0x68, 0x22, 0xde, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, - 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xf3, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x13, 0x65, 0x74, 0x68, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, - 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6c, 0x61, - 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x23, 0x0a, 0x0f, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, - 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, - 0x22, 0x96, 0x02, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, - 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, - 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, - 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x71, 0x0a, 0x16, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x8d, 0x01, 0x0a, - 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x87, 0x03, 0x0a, - 0x15, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x12, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, - 0x72, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, 0x1a, 0x93, 0x01, 0x0a, 0x10, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x12, - 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x29, 0x0a, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, - 0x72, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x12, - 0x1f, 0x0a, 0x0b, 0x65, 0x72, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x72, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x1a, 0x74, 0x0a, 0x15, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x55, 0x72, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, - 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, - 0x64, 0x22, 0xa5, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, - 0x79, 0x43, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, - 0x69, 0x73, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, - 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x43, - 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, - 0x5f, 0x63, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x22, 0x73, 0x0a, 0x21, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, - 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x5c, - 0x0a, 0x22, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x27, - 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x12, 0x34, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0xd0, 0x0f, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x00, 0x52, 0x06, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x4d, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0xd1, 0x0f, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x69, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x6a, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, + 0x6f, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x72, 0x0a, 0x18, 0x53, + 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, + 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, + 0x8f, 0x01, 0x0a, 0x13, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, + 0x36, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, + 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, + 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0xf5, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x44, 0x0a, 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x12, 0x4c, 0x65, + 0x67, 0x61, 0x63, 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4c, 0x0a, 0x14, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x6f, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x44, 0x0a, 0x10, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x68, 0x61, 0x73, + 0x68, 0x22, 0xde, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x62, 0x0a, 0x28, 0x47, 0x65, 0x74, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x13, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x05, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x22, 0x3c, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x24, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x67, - 0x6f, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6c, + 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, + 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x22, 0xf3, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x65, 0x74, 0x68, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, + 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, + 0x27, 0x0a, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x23, 0x0a, 0x0f, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x22, 0x96, 0x02, + 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x29, 0x0a, 0x10, + 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, + 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x63, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, + 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, + 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x71, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x39, + 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x39, + 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x87, 0x03, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x12, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x34, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, 0x1a, 0x93, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72, 0x6c, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, + 0x65, 0x72, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x65, 0x72, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x74, 0x0a, + 0x15, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x42, 0x79, 0x43, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x22, 0xa5, + 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, + 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x69, + 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74, + 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, + 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, + 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x22, 0x73, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, + 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x5c, 0x0a, 0x22, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x27, 0x47, 0x65, 0x74, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, + 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x22, 0x62, 0x0a, 0x28, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, + 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x22, 0x3c, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, + 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, + 0x70, 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -7589,7 +8347,7 @@ func file_core_v1_types_proto_rawDescGZIP() []byte { } var file_core_v1_types_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_core_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 101) +var file_core_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 111) var file_core_v1_types_proto_goTypes = []interface{}{ (GetStatusResponse_ProcessInfo_ProcessState)(0), // 0: core.v1.GetStatusResponse.ProcessInfo.ProcessState (GetStatusResponse_SyncInfo_StateSyncInfo_Phase)(0), // 1: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.Phase @@ -7659,81 +8417,91 @@ var file_core_v1_types_proto_goTypes = []interface{}{ (*GetPIERequest)(nil), // 65: core.v1.GetPIERequest (*GetPIEResponse)(nil), // 66: core.v1.GetPIEResponse (*RewardMessage)(nil), // 67: core.v1.RewardMessage - (*CreateReward)(nil), // 68: core.v1.CreateReward - (*DeleteReward)(nil), // 69: core.v1.DeleteReward - (*GetRewardRequest)(nil), // 70: core.v1.GetRewardRequest - (*GetRewardResponse)(nil), // 71: core.v1.GetRewardResponse - (*RewardAttestationSignature)(nil), // 72: core.v1.RewardAttestationSignature - (*UploadSignature)(nil), // 73: core.v1.UploadSignature - (*FileUpload)(nil), // 74: core.v1.FileUpload - (*GetStreamURLsSignature)(nil), // 75: core.v1.GetStreamURLsSignature - (*GetStreamURLsRequest)(nil), // 76: core.v1.GetStreamURLsRequest - (*GetStreamURLsResponse)(nil), // 77: core.v1.GetStreamURLsResponse - (*GetUploadByCIDRequest)(nil), // 78: core.v1.GetUploadByCIDRequest - (*GetUploadByCIDResponse)(nil), // 79: core.v1.GetUploadByCIDResponse - (*GetRewardSenderAttestationRequest)(nil), // 80: core.v1.GetRewardSenderAttestationRequest - (*GetRewardSenderAttestationResponse)(nil), // 81: core.v1.GetRewardSenderAttestationResponse - (*GetDeleteRewardSenderAttestationRequest)(nil), // 82: core.v1.GetDeleteRewardSenderAttestationRequest - (*GetDeleteRewardSenderAttestationResponse)(nil), // 83: core.v1.GetDeleteRewardSenderAttestationResponse - (*StreamBlocksRequest)(nil), // 84: core.v1.StreamBlocksRequest - (*StreamBlocksResponse)(nil), // 85: core.v1.StreamBlocksResponse - (*GetStatusResponse_ProcessInfo)(nil), // 86: core.v1.GetStatusResponse.ProcessInfo - (*GetStatusResponse_NodeInfo)(nil), // 87: core.v1.GetStatusResponse.NodeInfo - (*GetStatusResponse_ChainInfo)(nil), // 88: core.v1.GetStatusResponse.ChainInfo - (*GetStatusResponse_SyncInfo)(nil), // 89: core.v1.GetStatusResponse.SyncInfo - (*GetStatusResponse_PruningInfo)(nil), // 90: core.v1.GetStatusResponse.PruningInfo - (*GetStatusResponse_ResourceInfo)(nil), // 91: core.v1.GetStatusResponse.ResourceInfo - (*GetStatusResponse_MempoolInfo)(nil), // 92: core.v1.GetStatusResponse.MempoolInfo - (*GetStatusResponse_SnapshotInfo)(nil), // 93: core.v1.GetStatusResponse.SnapshotInfo - (*GetStatusResponse_PeerInfo)(nil), // 94: core.v1.GetStatusResponse.PeerInfo - (*GetStatusResponse_StorageInfo)(nil), // 95: core.v1.GetStatusResponse.StorageInfo - (*GetStatusResponse_ProcessInfo_ProcessStateInfo)(nil), // 96: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - (*GetStatusResponse_SyncInfo_StateSyncInfo)(nil), // 97: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo - (*GetStatusResponse_SyncInfo_BlockSyncInfo)(nil), // 98: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo - (*GetStatusResponse_PeerInfo_Peer)(nil), // 99: core.v1.GetStatusResponse.PeerInfo.Peer - nil, // 100: core.v1.GetBlocksResponse.BlocksEntry - (*GetStreamURLsResponse_EntityStreamURLs)(nil), // 101: core.v1.GetStreamURLsResponse.EntityStreamURLs - nil, // 102: core.v1.GetStreamURLsResponse.EntityStreamUrlsEntry - (*v1beta1.Transaction)(nil), // 103: core.v1beta1.Transaction - (*v1beta1.TransactionReceipt)(nil), // 104: core.v1beta1.TransactionReceipt - (*timestamppb.Timestamp)(nil), // 105: google.protobuf.Timestamp - (*v1beta11.NewReleaseMessage)(nil), // 106: ddex.v1beta1.NewReleaseMessage - (*v1beta11.Party)(nil), // 107: ddex.v1beta1.Party - (*v1beta11.Resource)(nil), // 108: ddex.v1beta1.Resource - (*v1beta11.Release)(nil), // 109: ddex.v1beta1.Release - (*v1beta11.Deal)(nil), // 110: ddex.v1beta1.Deal - (*v1beta11.MeadMessage)(nil), // 111: ddex.v1beta1.MeadMessage - (*v1beta11.PieMessage)(nil), // 112: ddex.v1beta1.PieMessage + (*RewardBody)(nil), // 68: core.v1.RewardBody + (*CreateReward)(nil), // 69: core.v1.CreateReward + (*DeleteReward)(nil), // 70: core.v1.DeleteReward + (*RewardPoolMessage)(nil), // 71: core.v1.RewardPoolMessage + (*RewardPoolBody)(nil), // 72: core.v1.RewardPoolBody + (*CreateRewardPool)(nil), // 73: core.v1.CreateRewardPool + (*SetRewardPoolAuthorities)(nil), // 74: core.v1.SetRewardPoolAuthorities + (*LegacyRewardMessage)(nil), // 75: core.v1.LegacyRewardMessage + (*LegacyCreateReward)(nil), // 76: core.v1.LegacyCreateReward + (*LegacyDeleteReward)(nil), // 77: core.v1.LegacyDeleteReward + (*GetRewardPoolRequest)(nil), // 78: core.v1.GetRewardPoolRequest + (*GetRewardPoolResponse)(nil), // 79: core.v1.GetRewardPoolResponse + (*GetRewardRequest)(nil), // 80: core.v1.GetRewardRequest + (*GetRewardResponse)(nil), // 81: core.v1.GetRewardResponse + (*RewardAttestationSignature)(nil), // 82: core.v1.RewardAttestationSignature + (*UploadSignature)(nil), // 83: core.v1.UploadSignature + (*FileUpload)(nil), // 84: core.v1.FileUpload + (*GetStreamURLsSignature)(nil), // 85: core.v1.GetStreamURLsSignature + (*GetStreamURLsRequest)(nil), // 86: core.v1.GetStreamURLsRequest + (*GetStreamURLsResponse)(nil), // 87: core.v1.GetStreamURLsResponse + (*GetUploadByCIDRequest)(nil), // 88: core.v1.GetUploadByCIDRequest + (*GetUploadByCIDResponse)(nil), // 89: core.v1.GetUploadByCIDResponse + (*GetRewardSenderAttestationRequest)(nil), // 90: core.v1.GetRewardSenderAttestationRequest + (*GetRewardSenderAttestationResponse)(nil), // 91: core.v1.GetRewardSenderAttestationResponse + (*GetDeleteRewardSenderAttestationRequest)(nil), // 92: core.v1.GetDeleteRewardSenderAttestationRequest + (*GetDeleteRewardSenderAttestationResponse)(nil), // 93: core.v1.GetDeleteRewardSenderAttestationResponse + (*StreamBlocksRequest)(nil), // 94: core.v1.StreamBlocksRequest + (*StreamBlocksResponse)(nil), // 95: core.v1.StreamBlocksResponse + (*GetStatusResponse_ProcessInfo)(nil), // 96: core.v1.GetStatusResponse.ProcessInfo + (*GetStatusResponse_NodeInfo)(nil), // 97: core.v1.GetStatusResponse.NodeInfo + (*GetStatusResponse_ChainInfo)(nil), // 98: core.v1.GetStatusResponse.ChainInfo + (*GetStatusResponse_SyncInfo)(nil), // 99: core.v1.GetStatusResponse.SyncInfo + (*GetStatusResponse_PruningInfo)(nil), // 100: core.v1.GetStatusResponse.PruningInfo + (*GetStatusResponse_ResourceInfo)(nil), // 101: core.v1.GetStatusResponse.ResourceInfo + (*GetStatusResponse_MempoolInfo)(nil), // 102: core.v1.GetStatusResponse.MempoolInfo + (*GetStatusResponse_SnapshotInfo)(nil), // 103: core.v1.GetStatusResponse.SnapshotInfo + (*GetStatusResponse_PeerInfo)(nil), // 104: core.v1.GetStatusResponse.PeerInfo + (*GetStatusResponse_StorageInfo)(nil), // 105: core.v1.GetStatusResponse.StorageInfo + (*GetStatusResponse_ProcessInfo_ProcessStateInfo)(nil), // 106: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + (*GetStatusResponse_SyncInfo_StateSyncInfo)(nil), // 107: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo + (*GetStatusResponse_SyncInfo_BlockSyncInfo)(nil), // 108: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo + (*GetStatusResponse_PeerInfo_Peer)(nil), // 109: core.v1.GetStatusResponse.PeerInfo.Peer + nil, // 110: core.v1.GetBlocksResponse.BlocksEntry + (*GetStreamURLsResponse_EntityStreamURLs)(nil), // 111: core.v1.GetStreamURLsResponse.EntityStreamURLs + nil, // 112: core.v1.GetStreamURLsResponse.EntityStreamUrlsEntry + (*v1beta1.Transaction)(nil), // 113: core.v1beta1.Transaction + (*v1beta1.TransactionReceipt)(nil), // 114: core.v1beta1.TransactionReceipt + (*timestamppb.Timestamp)(nil), // 115: google.protobuf.Timestamp + (*v1beta11.NewReleaseMessage)(nil), // 116: ddex.v1beta1.NewReleaseMessage + (*v1beta11.Party)(nil), // 117: ddex.v1beta1.Party + (*v1beta11.Resource)(nil), // 118: ddex.v1beta1.Resource + (*v1beta11.Release)(nil), // 119: ddex.v1beta1.Release + (*v1beta11.Deal)(nil), // 120: ddex.v1beta1.Deal + (*v1beta11.MeadMessage)(nil), // 121: ddex.v1beta1.MeadMessage + (*v1beta11.PieMessage)(nil), // 122: ddex.v1beta1.PieMessage } var file_core_v1_types_proto_depIdxs = []int32{ - 87, // 0: core.v1.GetStatusResponse.node_info:type_name -> core.v1.GetStatusResponse.NodeInfo - 88, // 1: core.v1.GetStatusResponse.chain_info:type_name -> core.v1.GetStatusResponse.ChainInfo - 89, // 2: core.v1.GetStatusResponse.sync_info:type_name -> core.v1.GetStatusResponse.SyncInfo - 90, // 3: core.v1.GetStatusResponse.pruning_info:type_name -> core.v1.GetStatusResponse.PruningInfo - 91, // 4: core.v1.GetStatusResponse.resource_info:type_name -> core.v1.GetStatusResponse.ResourceInfo - 92, // 5: core.v1.GetStatusResponse.mempool_info:type_name -> core.v1.GetStatusResponse.MempoolInfo - 94, // 6: core.v1.GetStatusResponse.peers:type_name -> core.v1.GetStatusResponse.PeerInfo - 93, // 7: core.v1.GetStatusResponse.snapshot_info:type_name -> core.v1.GetStatusResponse.SnapshotInfo - 86, // 8: core.v1.GetStatusResponse.process_info:type_name -> core.v1.GetStatusResponse.ProcessInfo - 95, // 9: core.v1.GetStatusResponse.storage_info:type_name -> core.v1.GetStatusResponse.StorageInfo + 97, // 0: core.v1.GetStatusResponse.node_info:type_name -> core.v1.GetStatusResponse.NodeInfo + 98, // 1: core.v1.GetStatusResponse.chain_info:type_name -> core.v1.GetStatusResponse.ChainInfo + 99, // 2: core.v1.GetStatusResponse.sync_info:type_name -> core.v1.GetStatusResponse.SyncInfo + 100, // 3: core.v1.GetStatusResponse.pruning_info:type_name -> core.v1.GetStatusResponse.PruningInfo + 101, // 4: core.v1.GetStatusResponse.resource_info:type_name -> core.v1.GetStatusResponse.ResourceInfo + 102, // 5: core.v1.GetStatusResponse.mempool_info:type_name -> core.v1.GetStatusResponse.MempoolInfo + 104, // 6: core.v1.GetStatusResponse.peers:type_name -> core.v1.GetStatusResponse.PeerInfo + 103, // 7: core.v1.GetStatusResponse.snapshot_info:type_name -> core.v1.GetStatusResponse.SnapshotInfo + 96, // 8: core.v1.GetStatusResponse.process_info:type_name -> core.v1.GetStatusResponse.ProcessInfo + 105, // 9: core.v1.GetStatusResponse.storage_info:type_name -> core.v1.GetStatusResponse.StorageInfo 24, // 10: core.v1.GetBlockResponse.block:type_name -> core.v1.Block - 100, // 11: core.v1.GetBlocksResponse.blocks:type_name -> core.v1.GetBlocksResponse.BlocksEntry + 110, // 11: core.v1.GetBlocksResponse.blocks:type_name -> core.v1.GetBlocksResponse.BlocksEntry 25, // 12: core.v1.GetTransactionResponse.transaction:type_name -> core.v1.Transaction 26, // 13: core.v1.SendTransactionRequest.transaction:type_name -> core.v1.SignedTransaction - 103, // 14: core.v1.SendTransactionRequest.transactionv2:type_name -> core.v1beta1.Transaction + 113, // 14: core.v1.SendTransactionRequest.transactionv2:type_name -> core.v1beta1.Transaction 25, // 15: core.v1.SendTransactionResponse.transaction:type_name -> core.v1.Transaction - 104, // 16: core.v1.SendTransactionResponse.transaction_receipt:type_name -> core.v1beta1.TransactionReceipt + 114, // 16: core.v1.SendTransactionResponse.transaction_receipt:type_name -> core.v1beta1.TransactionReceipt 26, // 17: core.v1.ForwardTransactionRequest.transaction:type_name -> core.v1.SignedTransaction - 103, // 18: core.v1.ForwardTransactionRequest.transactionv2:type_name -> core.v1beta1.Transaction + 113, // 18: core.v1.ForwardTransactionRequest.transactionv2:type_name -> core.v1beta1.Transaction 37, // 19: core.v1.GetRegistrationAttestationRequest.registration:type_name -> core.v1.ValidatorRegistration 37, // 20: core.v1.GetRegistrationAttestationResponse.registration:type_name -> core.v1.ValidatorRegistration 38, // 21: core.v1.GetDeregistrationAttestationRequest.deregistration:type_name -> core.v1.ValidatorDeregistration 38, // 22: core.v1.GetDeregistrationAttestationResponse.deregistration:type_name -> core.v1.ValidatorDeregistration - 105, // 23: core.v1.Block.timestamp:type_name -> google.protobuf.Timestamp + 115, // 23: core.v1.Block.timestamp:type_name -> google.protobuf.Timestamp 25, // 24: core.v1.Block.transactions:type_name -> core.v1.Transaction 26, // 25: core.v1.Transaction.transaction:type_name -> core.v1.SignedTransaction - 105, // 26: core.v1.Transaction.timestamp:type_name -> google.protobuf.Timestamp - 103, // 27: core.v1.Transaction.transactionv2:type_name -> core.v1beta1.Transaction + 115, // 26: core.v1.Transaction.timestamp:type_name -> google.protobuf.Timestamp + 113, // 27: core.v1.Transaction.transactionv2:type_name -> core.v1beta1.Transaction 27, // 28: core.v1.SignedTransaction.plays:type_name -> core.v1.TrackPlays 28, // 29: core.v1.SignedTransaction.validator_registration:type_name -> core.v1.ValidatorRegistrationLegacy 30, // 30: core.v1.SignedTransaction.sla_rollup:type_name -> core.v1.SlaRollup @@ -7742,68 +8510,75 @@ var file_core_v1_types_proto_depIdxs = []int32{ 34, // 33: core.v1.SignedTransaction.storage_proof:type_name -> core.v1.StorageProof 35, // 34: core.v1.SignedTransaction.storage_proof_verification:type_name -> core.v1.StorageProofVerification 36, // 35: core.v1.SignedTransaction.attestation:type_name -> core.v1.Attestation - 106, // 36: core.v1.SignedTransaction.release:type_name -> ddex.v1beta1.NewReleaseMessage + 116, // 36: core.v1.SignedTransaction.release:type_name -> ddex.v1beta1.NewReleaseMessage 67, // 37: core.v1.SignedTransaction.reward:type_name -> core.v1.RewardMessage - 74, // 38: core.v1.SignedTransaction.file_upload:type_name -> core.v1.FileUpload - 29, // 39: core.v1.TrackPlays.plays:type_name -> core.v1.TrackPlay - 105, // 40: core.v1.TrackPlay.timestamp:type_name -> google.protobuf.Timestamp - 105, // 41: core.v1.SlaRollup.timestamp:type_name -> google.protobuf.Timestamp - 31, // 42: core.v1.SlaRollup.reports:type_name -> core.v1.SlaNodeReport - 37, // 43: core.v1.Attestation.validator_registration:type_name -> core.v1.ValidatorRegistration - 38, // 44: core.v1.Attestation.validator_deregistration:type_name -> core.v1.ValidatorDeregistration - 41, // 45: core.v1.GetStoredSnapshotsResponse.snapshots:type_name -> core.v1.SnapshotMetadata - 42, // 46: core.v1.Reward.claim_authorities:type_name -> core.v1.ClaimAuthority - 71, // 47: core.v1.GetRewardsResponse.rewards:type_name -> core.v1.GetRewardResponse - 105, // 48: core.v1.SlashRecommendation.start:type_name -> google.protobuf.Timestamp - 105, // 49: core.v1.SlashRecommendation.end:type_name -> google.protobuf.Timestamp - 48, // 50: core.v1.GetSlashAttestationRequest.data:type_name -> core.v1.SlashRecommendation - 49, // 51: core.v1.GetSlashAttestationsRequest.request:type_name -> core.v1.GetSlashAttestationRequest - 50, // 52: core.v1.GetSlashAttestationsResponse.attestations:type_name -> core.v1.GetSlashAttestationResponse - 106, // 53: core.v1.GetERNResponse.ern:type_name -> ddex.v1beta1.NewReleaseMessage - 107, // 54: core.v1.GetPartyResponse.party:type_name -> ddex.v1beta1.Party - 108, // 55: core.v1.GetResourceResponse.resource:type_name -> ddex.v1beta1.Resource - 109, // 56: core.v1.GetReleaseResponse.release:type_name -> ddex.v1beta1.Release - 110, // 57: core.v1.GetDealResponse.deal:type_name -> ddex.v1beta1.Deal - 111, // 58: core.v1.GetMEADResponse.mead:type_name -> ddex.v1beta1.MeadMessage - 112, // 59: core.v1.GetPIEResponse.pie:type_name -> ddex.v1beta1.PieMessage - 68, // 60: core.v1.RewardMessage.create:type_name -> core.v1.CreateReward - 69, // 61: core.v1.RewardMessage.delete:type_name -> core.v1.DeleteReward - 42, // 62: core.v1.CreateReward.claim_authorities:type_name -> core.v1.ClaimAuthority - 105, // 63: core.v1.GetStreamURLsSignature.expires_at:type_name -> google.protobuf.Timestamp - 105, // 64: core.v1.GetStreamURLsRequest.expires_at:type_name -> google.protobuf.Timestamp - 102, // 65: core.v1.GetStreamURLsResponse.entity_stream_urls:type_name -> core.v1.GetStreamURLsResponse.EntityStreamUrlsEntry - 24, // 66: core.v1.StreamBlocksResponse.block:type_name -> core.v1.Block - 96, // 67: core.v1.GetStatusResponse.ProcessInfo.abci:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 68: core.v1.GetStatusResponse.ProcessInfo.registry_bridge:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 69: core.v1.GetStatusResponse.ProcessInfo.echo_server:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 70: core.v1.GetStatusResponse.ProcessInfo.sync_tasks:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 71: core.v1.GetStatusResponse.ProcessInfo.peer_manager:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 72: core.v1.GetStatusResponse.ProcessInfo.data_companion:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 73: core.v1.GetStatusResponse.ProcessInfo.cache:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 74: core.v1.GetStatusResponse.ProcessInfo.log_sync:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 75: core.v1.GetStatusResponse.ProcessInfo.state_sync:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 96, // 76: core.v1.GetStatusResponse.ProcessInfo.mempool_cache:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo - 97, // 77: core.v1.GetStatusResponse.SyncInfo.state_sync:type_name -> core.v1.GetStatusResponse.SyncInfo.StateSyncInfo - 98, // 78: core.v1.GetStatusResponse.SyncInfo.block_sync:type_name -> core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo - 41, // 79: core.v1.GetStatusResponse.SnapshotInfo.snapshots:type_name -> core.v1.SnapshotMetadata - 99, // 80: core.v1.GetStatusResponse.PeerInfo.peers:type_name -> core.v1.GetStatusResponse.PeerInfo.Peer - 0, // 81: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo.state:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessState - 105, // 82: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo.started_at:type_name -> google.protobuf.Timestamp - 105, // 83: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo.completed_at:type_name -> google.protobuf.Timestamp - 1, // 84: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.phase:type_name -> core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.Phase - 41, // 85: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.snapshot:type_name -> core.v1.SnapshotMetadata - 105, // 86: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.started_at:type_name -> google.protobuf.Timestamp - 105, // 87: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.completed_at:type_name -> google.protobuf.Timestamp - 87, // 88: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo.head_source:type_name -> core.v1.GetStatusResponse.NodeInfo - 105, // 89: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo.started_at:type_name -> google.protobuf.Timestamp - 105, // 90: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo.completed_at:type_name -> google.protobuf.Timestamp - 24, // 91: core.v1.GetBlocksResponse.BlocksEntry.value:type_name -> core.v1.Block - 101, // 92: core.v1.GetStreamURLsResponse.EntityStreamUrlsEntry.value:type_name -> core.v1.GetStreamURLsResponse.EntityStreamURLs - 93, // [93:93] is the sub-list for method output_type - 93, // [93:93] is the sub-list for method input_type - 93, // [93:93] is the sub-list for extension type_name - 93, // [93:93] is the sub-list for extension extendee - 0, // [0:93] is the sub-list for field type_name + 84, // 38: core.v1.SignedTransaction.file_upload:type_name -> core.v1.FileUpload + 71, // 39: core.v1.SignedTransaction.reward_pool:type_name -> core.v1.RewardPoolMessage + 29, // 40: core.v1.TrackPlays.plays:type_name -> core.v1.TrackPlay + 115, // 41: core.v1.TrackPlay.timestamp:type_name -> google.protobuf.Timestamp + 115, // 42: core.v1.SlaRollup.timestamp:type_name -> google.protobuf.Timestamp + 31, // 43: core.v1.SlaRollup.reports:type_name -> core.v1.SlaNodeReport + 37, // 44: core.v1.Attestation.validator_registration:type_name -> core.v1.ValidatorRegistration + 38, // 45: core.v1.Attestation.validator_deregistration:type_name -> core.v1.ValidatorDeregistration + 41, // 46: core.v1.GetStoredSnapshotsResponse.snapshots:type_name -> core.v1.SnapshotMetadata + 42, // 47: core.v1.Reward.claim_authorities:type_name -> core.v1.ClaimAuthority + 81, // 48: core.v1.GetRewardsResponse.rewards:type_name -> core.v1.GetRewardResponse + 115, // 49: core.v1.SlashRecommendation.start:type_name -> google.protobuf.Timestamp + 115, // 50: core.v1.SlashRecommendation.end:type_name -> google.protobuf.Timestamp + 48, // 51: core.v1.GetSlashAttestationRequest.data:type_name -> core.v1.SlashRecommendation + 49, // 52: core.v1.GetSlashAttestationsRequest.request:type_name -> core.v1.GetSlashAttestationRequest + 50, // 53: core.v1.GetSlashAttestationsResponse.attestations:type_name -> core.v1.GetSlashAttestationResponse + 116, // 54: core.v1.GetERNResponse.ern:type_name -> ddex.v1beta1.NewReleaseMessage + 117, // 55: core.v1.GetPartyResponse.party:type_name -> ddex.v1beta1.Party + 118, // 56: core.v1.GetResourceResponse.resource:type_name -> ddex.v1beta1.Resource + 119, // 57: core.v1.GetReleaseResponse.release:type_name -> ddex.v1beta1.Release + 120, // 58: core.v1.GetDealResponse.deal:type_name -> ddex.v1beta1.Deal + 121, // 59: core.v1.GetMEADResponse.mead:type_name -> ddex.v1beta1.MeadMessage + 122, // 60: core.v1.GetPIEResponse.pie:type_name -> ddex.v1beta1.PieMessage + 68, // 61: core.v1.RewardMessage.body:type_name -> core.v1.RewardBody + 69, // 62: core.v1.RewardBody.create:type_name -> core.v1.CreateReward + 70, // 63: core.v1.RewardBody.delete:type_name -> core.v1.DeleteReward + 72, // 64: core.v1.RewardPoolMessage.body:type_name -> core.v1.RewardPoolBody + 73, // 65: core.v1.RewardPoolBody.create:type_name -> core.v1.CreateRewardPool + 74, // 66: core.v1.RewardPoolBody.set_authorities:type_name -> core.v1.SetRewardPoolAuthorities + 76, // 67: core.v1.LegacyRewardMessage.create:type_name -> core.v1.LegacyCreateReward + 77, // 68: core.v1.LegacyRewardMessage.delete:type_name -> core.v1.LegacyDeleteReward + 42, // 69: core.v1.LegacyCreateReward.claim_authorities:type_name -> core.v1.ClaimAuthority + 115, // 70: core.v1.GetStreamURLsSignature.expires_at:type_name -> google.protobuf.Timestamp + 115, // 71: core.v1.GetStreamURLsRequest.expires_at:type_name -> google.protobuf.Timestamp + 112, // 72: core.v1.GetStreamURLsResponse.entity_stream_urls:type_name -> core.v1.GetStreamURLsResponse.EntityStreamUrlsEntry + 24, // 73: core.v1.StreamBlocksResponse.block:type_name -> core.v1.Block + 106, // 74: core.v1.GetStatusResponse.ProcessInfo.abci:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 75: core.v1.GetStatusResponse.ProcessInfo.registry_bridge:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 76: core.v1.GetStatusResponse.ProcessInfo.echo_server:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 77: core.v1.GetStatusResponse.ProcessInfo.sync_tasks:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 78: core.v1.GetStatusResponse.ProcessInfo.peer_manager:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 79: core.v1.GetStatusResponse.ProcessInfo.data_companion:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 80: core.v1.GetStatusResponse.ProcessInfo.cache:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 81: core.v1.GetStatusResponse.ProcessInfo.log_sync:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 82: core.v1.GetStatusResponse.ProcessInfo.state_sync:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 106, // 83: core.v1.GetStatusResponse.ProcessInfo.mempool_cache:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo + 107, // 84: core.v1.GetStatusResponse.SyncInfo.state_sync:type_name -> core.v1.GetStatusResponse.SyncInfo.StateSyncInfo + 108, // 85: core.v1.GetStatusResponse.SyncInfo.block_sync:type_name -> core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo + 41, // 86: core.v1.GetStatusResponse.SnapshotInfo.snapshots:type_name -> core.v1.SnapshotMetadata + 109, // 87: core.v1.GetStatusResponse.PeerInfo.peers:type_name -> core.v1.GetStatusResponse.PeerInfo.Peer + 0, // 88: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo.state:type_name -> core.v1.GetStatusResponse.ProcessInfo.ProcessState + 115, // 89: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo.started_at:type_name -> google.protobuf.Timestamp + 115, // 90: core.v1.GetStatusResponse.ProcessInfo.ProcessStateInfo.completed_at:type_name -> google.protobuf.Timestamp + 1, // 91: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.phase:type_name -> core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.Phase + 41, // 92: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.snapshot:type_name -> core.v1.SnapshotMetadata + 115, // 93: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.started_at:type_name -> google.protobuf.Timestamp + 115, // 94: core.v1.GetStatusResponse.SyncInfo.StateSyncInfo.completed_at:type_name -> google.protobuf.Timestamp + 97, // 95: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo.head_source:type_name -> core.v1.GetStatusResponse.NodeInfo + 115, // 96: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo.started_at:type_name -> google.protobuf.Timestamp + 115, // 97: core.v1.GetStatusResponse.SyncInfo.BlockSyncInfo.completed_at:type_name -> google.protobuf.Timestamp + 24, // 98: core.v1.GetBlocksResponse.BlocksEntry.value:type_name -> core.v1.Block + 111, // 99: core.v1.GetStreamURLsResponse.EntityStreamUrlsEntry.value:type_name -> core.v1.GetStreamURLsResponse.EntityStreamURLs + 100, // [100:100] is the sub-list for method output_type + 100, // [100:100] is the sub-list for method input_type + 100, // [100:100] is the sub-list for extension type_name + 100, // [100:100] is the sub-list for extension extendee + 0, // [0:100] is the sub-list for field type_name } func init() { file_core_v1_types_proto_init() } @@ -8605,7 +9380,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateReward); i { + switch v := v.(*RewardBody); i { case 0: return &v.state case 1: @@ -8617,7 +9392,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteReward); i { + switch v := v.(*CreateReward); i { case 0: return &v.state case 1: @@ -8629,7 +9404,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRewardRequest); i { + switch v := v.(*DeleteReward); i { case 0: return &v.state case 1: @@ -8641,7 +9416,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRewardResponse); i { + switch v := v.(*RewardPoolMessage); i { case 0: return &v.state case 1: @@ -8653,7 +9428,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RewardAttestationSignature); i { + switch v := v.(*RewardPoolBody); i { case 0: return &v.state case 1: @@ -8665,7 +9440,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UploadSignature); i { + switch v := v.(*CreateRewardPool); i { case 0: return &v.state case 1: @@ -8677,7 +9452,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FileUpload); i { + switch v := v.(*SetRewardPoolAuthorities); i { case 0: return &v.state case 1: @@ -8689,7 +9464,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStreamURLsSignature); i { + switch v := v.(*LegacyRewardMessage); i { case 0: return &v.state case 1: @@ -8701,7 +9476,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStreamURLsRequest); i { + switch v := v.(*LegacyCreateReward); i { case 0: return &v.state case 1: @@ -8713,7 +9488,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStreamURLsResponse); i { + switch v := v.(*LegacyDeleteReward); i { case 0: return &v.state case 1: @@ -8725,7 +9500,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUploadByCIDRequest); i { + switch v := v.(*GetRewardPoolRequest); i { case 0: return &v.state case 1: @@ -8737,7 +9512,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUploadByCIDResponse); i { + switch v := v.(*GetRewardPoolResponse); i { case 0: return &v.state case 1: @@ -8749,7 +9524,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRewardSenderAttestationRequest); i { + switch v := v.(*GetRewardRequest); i { case 0: return &v.state case 1: @@ -8761,7 +9536,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRewardSenderAttestationResponse); i { + switch v := v.(*GetRewardResponse); i { case 0: return &v.state case 1: @@ -8773,7 +9548,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetDeleteRewardSenderAttestationRequest); i { + switch v := v.(*RewardAttestationSignature); i { case 0: return &v.state case 1: @@ -8785,7 +9560,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetDeleteRewardSenderAttestationResponse); i { + switch v := v.(*UploadSignature); i { case 0: return &v.state case 1: @@ -8797,7 +9572,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamBlocksRequest); i { + switch v := v.(*FileUpload); i { case 0: return &v.state case 1: @@ -8809,7 +9584,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamBlocksResponse); i { + switch v := v.(*GetStreamURLsSignature); i { case 0: return &v.state case 1: @@ -8821,7 +9596,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_ProcessInfo); i { + switch v := v.(*GetStreamURLsRequest); i { case 0: return &v.state case 1: @@ -8833,7 +9608,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_NodeInfo); i { + switch v := v.(*GetStreamURLsResponse); i { case 0: return &v.state case 1: @@ -8845,7 +9620,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_ChainInfo); i { + switch v := v.(*GetUploadByCIDRequest); i { case 0: return &v.state case 1: @@ -8857,7 +9632,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_SyncInfo); i { + switch v := v.(*GetUploadByCIDResponse); i { case 0: return &v.state case 1: @@ -8869,7 +9644,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_PruningInfo); i { + switch v := v.(*GetRewardSenderAttestationRequest); i { case 0: return &v.state case 1: @@ -8881,7 +9656,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_ResourceInfo); i { + switch v := v.(*GetRewardSenderAttestationResponse); i { case 0: return &v.state case 1: @@ -8893,7 +9668,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_MempoolInfo); i { + switch v := v.(*GetDeleteRewardSenderAttestationRequest); i { case 0: return &v.state case 1: @@ -8905,7 +9680,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_SnapshotInfo); i { + switch v := v.(*GetDeleteRewardSenderAttestationResponse); i { case 0: return &v.state case 1: @@ -8917,7 +9692,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_PeerInfo); i { + switch v := v.(*StreamBlocksRequest); i { case 0: return &v.state case 1: @@ -8929,7 +9704,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_StorageInfo); i { + switch v := v.(*StreamBlocksResponse); i { case 0: return &v.state case 1: @@ -8941,7 +9716,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_ProcessInfo_ProcessStateInfo); i { + switch v := v.(*GetStatusResponse_ProcessInfo); i { case 0: return &v.state case 1: @@ -8953,7 +9728,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_SyncInfo_StateSyncInfo); i { + switch v := v.(*GetStatusResponse_NodeInfo); i { case 0: return &v.state case 1: @@ -8965,7 +9740,7 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_SyncInfo_BlockSyncInfo); i { + switch v := v.(*GetStatusResponse_ChainInfo); i { case 0: return &v.state case 1: @@ -8977,7 +9752,19 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatusResponse_PeerInfo_Peer); i { + switch v := v.(*GetStatusResponse_SyncInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_PruningInfo); i { case 0: return &v.state case 1: @@ -8989,6 +9776,114 @@ func file_core_v1_types_proto_init() { } } file_core_v1_types_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_ResourceInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_MempoolInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_SnapshotInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_PeerInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_StorageInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_ProcessInfo_ProcessStateInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_SyncInfo_StateSyncInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_SyncInfo_BlockSyncInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatusResponse_PeerInfo_Peer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_core_v1_types_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetStreamURLsResponse_EntityStreamURLs); i { case 0: return &v.state @@ -9013,16 +9908,25 @@ func file_core_v1_types_proto_init() { (*SignedTransaction_Release)(nil), (*SignedTransaction_Reward)(nil), (*SignedTransaction_FileUpload)(nil), + (*SignedTransaction_RewardPool)(nil), } file_core_v1_types_proto_msgTypes[34].OneofWrappers = []interface{}{ (*Attestation_ValidatorRegistration)(nil), (*Attestation_ValidatorDeregistration)(nil), } - file_core_v1_types_proto_msgTypes[65].OneofWrappers = []interface{}{ - (*RewardMessage_Create)(nil), - (*RewardMessage_Delete)(nil), + file_core_v1_types_proto_msgTypes[66].OneofWrappers = []interface{}{ + (*RewardBody_Create)(nil), + (*RewardBody_Delete)(nil), + } + file_core_v1_types_proto_msgTypes[70].OneofWrappers = []interface{}{ + (*RewardPoolBody_Create)(nil), + (*RewardPoolBody_SetAuthorities)(nil), + } + file_core_v1_types_proto_msgTypes[73].OneofWrappers = []interface{}{ + (*LegacyRewardMessage_Create)(nil), + (*LegacyRewardMessage_Delete)(nil), } - file_core_v1_types_proto_msgTypes[87].OneofWrappers = []interface{}{ + file_core_v1_types_proto_msgTypes[97].OneofWrappers = []interface{}{ (*GetStatusResponse_SyncInfo_StateSync)(nil), (*GetStatusResponse_SyncInfo_BlockSync)(nil), } @@ -9032,7 +9936,7 @@ func file_core_v1_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_core_v1_types_proto_rawDesc, NumEnums: 2, - NumMessages: 101, + NumMessages: 111, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/api/core/v1/v1connect/service.connect.go b/pkg/api/core/v1/v1connect/service.connect.go index 52dbcffe..80f1787f 100644 --- a/pkg/api/core/v1/v1connect/service.connect.go +++ b/pkg/api/core/v1/v1connect/service.connect.go @@ -79,6 +79,9 @@ const ( CoreServiceGetRewardProcedure = "/core.v1.CoreService/GetReward" // CoreServiceGetRewardsProcedure is the fully-qualified name of the CoreService's GetRewards RPC. CoreServiceGetRewardsProcedure = "/core.v1.CoreService/GetRewards" + // CoreServiceGetRewardPoolProcedure is the fully-qualified name of the CoreService's GetRewardPool + // RPC. + CoreServiceGetRewardPoolProcedure = "/core.v1.CoreService/GetRewardPool" // CoreServiceGetRewardAttestationProcedure is the fully-qualified name of the CoreService's // GetRewardAttestation RPC. CoreServiceGetRewardAttestationProcedure = "/core.v1.CoreService/GetRewardAttestation" @@ -120,6 +123,7 @@ type CoreServiceClient interface { GetPIE(context.Context, *connect.Request[v1.GetPIERequest]) (*connect.Response[v1.GetPIEResponse], error) GetReward(context.Context, *connect.Request[v1.GetRewardRequest]) (*connect.Response[v1.GetRewardResponse], error) GetRewards(context.Context, *connect.Request[v1.GetRewardsRequest]) (*connect.Response[v1.GetRewardsResponse], error) + GetRewardPool(context.Context, *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) GetRewardAttestation(context.Context, *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) GetRewardSenderAttestation(context.Context, *connect.Request[v1.GetRewardSenderAttestationRequest]) (*connect.Response[v1.GetRewardSenderAttestationResponse], error) GetDeleteRewardSenderAttestation(context.Context, *connect.Request[v1.GetDeleteRewardSenderAttestationRequest]) (*connect.Response[v1.GetDeleteRewardSenderAttestationResponse], error) @@ -253,6 +257,12 @@ func NewCoreServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. connect.WithSchema(coreServiceMethods.ByName("GetRewards")), connect.WithClientOptions(opts...), ), + getRewardPool: connect.NewClient[v1.GetRewardPoolRequest, v1.GetRewardPoolResponse]( + httpClient, + baseURL+CoreServiceGetRewardPoolProcedure, + connect.WithSchema(coreServiceMethods.ByName("GetRewardPool")), + connect.WithClientOptions(opts...), + ), getRewardAttestation: connect.NewClient[v1.GetRewardAttestationRequest, v1.GetRewardAttestationResponse]( httpClient, baseURL+CoreServiceGetRewardAttestationProcedure, @@ -313,6 +323,7 @@ type coreServiceClient struct { getPIE *connect.Client[v1.GetPIERequest, v1.GetPIEResponse] getReward *connect.Client[v1.GetRewardRequest, v1.GetRewardResponse] getRewards *connect.Client[v1.GetRewardsRequest, v1.GetRewardsResponse] + getRewardPool *connect.Client[v1.GetRewardPoolRequest, v1.GetRewardPoolResponse] getRewardAttestation *connect.Client[v1.GetRewardAttestationRequest, v1.GetRewardAttestationResponse] getRewardSenderAttestation *connect.Client[v1.GetRewardSenderAttestationRequest, v1.GetRewardSenderAttestationResponse] getDeleteRewardSenderAttestation *connect.Client[v1.GetDeleteRewardSenderAttestationRequest, v1.GetDeleteRewardSenderAttestationResponse] @@ -416,6 +427,11 @@ func (c *coreServiceClient) GetRewards(ctx context.Context, req *connect.Request return c.getRewards.CallUnary(ctx, req) } +// GetRewardPool calls core.v1.CoreService.GetRewardPool. +func (c *coreServiceClient) GetRewardPool(ctx context.Context, req *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) { + return c.getRewardPool.CallUnary(ctx, req) +} + // GetRewardAttestation calls core.v1.CoreService.GetRewardAttestation. func (c *coreServiceClient) GetRewardAttestation(ctx context.Context, req *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) { return c.getRewardAttestation.CallUnary(ctx, req) @@ -467,6 +483,7 @@ type CoreServiceHandler interface { GetPIE(context.Context, *connect.Request[v1.GetPIERequest]) (*connect.Response[v1.GetPIEResponse], error) GetReward(context.Context, *connect.Request[v1.GetRewardRequest]) (*connect.Response[v1.GetRewardResponse], error) GetRewards(context.Context, *connect.Request[v1.GetRewardsRequest]) (*connect.Response[v1.GetRewardsResponse], error) + GetRewardPool(context.Context, *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) GetRewardAttestation(context.Context, *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) GetRewardSenderAttestation(context.Context, *connect.Request[v1.GetRewardSenderAttestationRequest]) (*connect.Response[v1.GetRewardSenderAttestationResponse], error) GetDeleteRewardSenderAttestation(context.Context, *connect.Request[v1.GetDeleteRewardSenderAttestationRequest]) (*connect.Response[v1.GetDeleteRewardSenderAttestationResponse], error) @@ -596,6 +613,12 @@ func NewCoreServiceHandler(svc CoreServiceHandler, opts ...connect.HandlerOption connect.WithSchema(coreServiceMethods.ByName("GetRewards")), connect.WithHandlerOptions(opts...), ) + coreServiceGetRewardPoolHandler := connect.NewUnaryHandler( + CoreServiceGetRewardPoolProcedure, + svc.GetRewardPool, + connect.WithSchema(coreServiceMethods.ByName("GetRewardPool")), + connect.WithHandlerOptions(opts...), + ) coreServiceGetRewardAttestationHandler := connect.NewUnaryHandler( CoreServiceGetRewardAttestationProcedure, svc.GetRewardAttestation, @@ -672,6 +695,8 @@ func NewCoreServiceHandler(svc CoreServiceHandler, opts ...connect.HandlerOption coreServiceGetRewardHandler.ServeHTTP(w, r) case CoreServiceGetRewardsProcedure: coreServiceGetRewardsHandler.ServeHTTP(w, r) + case CoreServiceGetRewardPoolProcedure: + coreServiceGetRewardPoolHandler.ServeHTTP(w, r) case CoreServiceGetRewardAttestationProcedure: coreServiceGetRewardAttestationHandler.ServeHTTP(w, r) case CoreServiceGetRewardSenderAttestationProcedure: @@ -769,6 +794,10 @@ func (UnimplementedCoreServiceHandler) GetRewards(context.Context, *connect.Requ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.CoreService.GetRewards is not implemented")) } +func (UnimplementedCoreServiceHandler) GetRewardPool(context.Context, *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.CoreService.GetRewardPool is not implemented")) +} + func (UnimplementedCoreServiceHandler) GetRewardAttestation(context.Context, *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.CoreService.GetRewardAttestation is not implemented")) } diff --git a/pkg/common/legacy_reward_signing.go b/pkg/common/legacy_reward_signing.go new file mode 100644 index 00000000..fd8b6d96 --- /dev/null +++ b/pkg/common/legacy_reward_signing.go @@ -0,0 +1,69 @@ +package common + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "sort" + + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" +) + +// Legacy reward signing scheme used by the pre-pool-rollout network. Kept +// here so the new binary can recover signers from historical reward +// transactions during block-sync-from-genesis. New code paths must not use +// this scheme — sign / verify against the body+signature envelope instead. +// +// The scheme is a pipe-delimited canonical string of the action's fields +// (with claim authorities sorted and JSON-encoded), sha256-hashed, and +// signed with EthSign / recovered with EthRecover. It is NOT +// proto-deterministic and depends on the field set of the legacy proto +// types staying frozen. + +func LegacyDeterministicCreateRewardData(cr *corev1.LegacyCreateReward) string { + authorities := make([]string, len(cr.ClaimAuthorities)) + for i, auth := range cr.ClaimAuthorities { + authorities[i] = fmt.Sprintf("%s:%s", auth.Address, auth.Name) + } + sort.Strings(authorities) + authoritiesJson, _ := json.Marshal(authorities) + data := fmt.Sprintf("%s|%s|%d|%s|%d", + cr.RewardId, + cr.Name, + cr.Amount, + string(authoritiesJson), + cr.DeadlineBlockHeight) + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:]) +} + +func LegacyDeterministicDeleteRewardData(dr *corev1.LegacyDeleteReward) string { + data := fmt.Sprintf("%s|%d", dr.Address, dr.DeadlineBlockHeight) + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:]) +} + +// LegacyRecoverCreateReward returns the eth address that signed a legacy +// CreateReward transaction. +func LegacyRecoverCreateReward(cr *corev1.LegacyCreateReward) (string, error) { + signatureData := LegacyDeterministicCreateRewardData(cr) + dataBytes, err := hex.DecodeString(signatureData) + if err != nil { + return "", fmt.Errorf("invalid hex data: %w", err) + } + _, address, err := EthRecover(cr.Signature, dataBytes) + return address, err +} + +// LegacyRecoverDeleteReward returns the eth address that signed a legacy +// DeleteReward transaction. +func LegacyRecoverDeleteReward(dr *corev1.LegacyDeleteReward) (string, error) { + signatureData := LegacyDeterministicDeleteRewardData(dr) + dataBytes, err := hex.DecodeString(signatureData) + if err != nil { + return "", fmt.Errorf("invalid hex data: %w", err) + } + _, address, err := EthRecover(dr.Signature, dataBytes) + return address, err +} diff --git a/pkg/common/proto.go b/pkg/common/proto.go index e72d397c..7c496f44 100644 --- a/pkg/common/proto.go +++ b/pkg/common/proto.go @@ -19,29 +19,41 @@ func ToTxHashFromBytes(txBytes []byte) TxHash { return bytes.HexBytes(hash).String() } -// ProtoTxSign signs a protobuf message for transaction purposes -// WARNING: This should only be used when sending transactions and not replaying them -// as it's not safe from protobuf evolution. The message structure could change -// between versions, making signatures invalid. -func ProtoSign(pkey *ecdsa.PrivateKey, msg proto.Message) (string, error) { - msgBytes, err := proto.Marshal(msg) +// ProtoSign deterministically marshals body and signs the bytes with privKey. +// Callers pass the body (RewardBody, RewardPoolBody, etc.) — the body never +// contains its own signature, so there's no chicken-and-egg. The signature +// goes alongside the body in its envelope (RewardMessage, RewardPoolMessage) +// at transport time. +// +// Forward-compat: proto3 omits default-valued fields, so additive-only schema +// changes (new optional fields) don't shift the bytes for old signers that +// leave them unset. Existing signatures continue to verify as long as no +// caller removes / renames / changes-type-of an existing field. +// +// Cross-action replay protection: two action types share an envelope via a +// `oneof`, whose field tag is part of the marshaled bytes. Two actions with +// otherwise-identical inner shapes produce different signed bytes. +func ProtoSign(privKey *ecdsa.PrivateKey, body proto.Message) (string, error) { + b, err := signableBytes(body) if err != nil { - return "", fmt.Errorf("failed to marshal protobuf message: %w", err) + return "", fmt.Errorf("marshal for signing: %w", err) } - - return EthSign(pkey, msgBytes) + return EthSign(privKey, b) } -// ProtoTxRecover recovers the signer address from a protobuf message and signature -// WARNING: This should only be used when verifying transactions and not replaying them -// as it's not safe from protobuf evolution. The message structure could change -// between versions, making signatures invalid. -func ProtoRecover(msg proto.Message, signature string) (string, error) { - msgBytes, err := proto.Marshal(msg) +// ProtoRecover re-marshals body and recovers the eth address that produced +// signature. Returns the recovered address; the caller is responsible for +// any membership / authorization check. +func ProtoRecover(body proto.Message, signature string) (string, error) { + b, err := signableBytes(body) if err != nil { - return "", fmt.Errorf("failed to marshal protobuf message: %w", err) + return "", fmt.Errorf("marshal for recovery: %w", err) } - - _, address, err := EthRecover(signature, msgBytes) + _, address, err := EthRecover(signature, b) return address, err } + +func signableBytes(body proto.Message) ([]byte, error) { + opts := proto.MarshalOptions{Deterministic: true} + return opts.Marshal(body) +} diff --git a/pkg/common/proto_test.go b/pkg/common/proto_test.go new file mode 100644 index 00000000..63c64965 --- /dev/null +++ b/pkg/common/proto_test.go @@ -0,0 +1,166 @@ +package common + +import ( + "strings" + "testing" + + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/ethereum/go-ethereum/crypto" +) + +// TestProtoSignRecover_Roundtrip exercises every signed body type through +// ProtoSign + ProtoRecover. Whichever key signed, ProtoRecover returns its +// eth address. +func TestProtoSignRecover_Roundtrip(t *testing.T) { + priv, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("generate key: %v", err) + } + signer := crypto.PubkeyToAddress(priv.PublicKey).Hex() + + t.Run("RewardBody_Create", func(t *testing.T) { + body := &corev1.RewardBody{ + DeadlineBlockHeight: 999_999, + Action: &corev1.RewardBody_Create{Create: &corev1.CreateReward{ + RewardId: "r-1", + Name: "Reward 1", + Amount: 1000, + RewardsManagerPubkey: "DJj6F8oHLQM7Ec7FNh3sKWHJDZG7uH1zH7bPq3p1mUe2", + }}, + } + sig, err := ProtoSign(priv, body) + if err != nil { + t.Fatalf("sign: %v", err) + } + got, err := ProtoRecover(body, sig) + if err != nil { + t.Fatalf("recover: %v", err) + } + if !strings.EqualFold(got, signer) { + t.Fatalf("recovered %q, want %q", got, signer) + } + }) + + t.Run("RewardBody_Delete", func(t *testing.T) { + body := &corev1.RewardBody{ + DeadlineBlockHeight: 100, + Action: &corev1.RewardBody_Delete{Delete: &corev1.DeleteReward{Address: "0xreward"}}, + } + sig, err := ProtoSign(priv, body) + if err != nil { + t.Fatalf("sign: %v", err) + } + got, err := ProtoRecover(body, sig) + if err != nil { + t.Fatalf("recover: %v", err) + } + if !strings.EqualFold(got, signer) { + t.Fatalf("recovered %q, want %q", got, signer) + } + }) + + t.Run("RewardPoolBody_Create", func(t *testing.T) { + body := &corev1.RewardPoolBody{ + DeadlineBlockHeight: 100, + Action: &corev1.RewardPoolBody_Create{Create: &corev1.CreateRewardPool{ + RewardsManagerPubkey: "DJj6F8oHLQM7Ec7FNh3sKWHJDZG7uH1zH7bPq3p1mUe2", + Authorities: []string{signer}, + }}, + } + sig, err := ProtoSign(priv, body) + if err != nil { + t.Fatalf("sign: %v", err) + } + got, err := ProtoRecover(body, sig) + if err != nil { + t.Fatalf("recover: %v", err) + } + if !strings.EqualFold(got, signer) { + t.Fatalf("recovered %q, want %q", got, signer) + } + }) + + t.Run("RewardPoolBody_SetAuthorities", func(t *testing.T) { + body := &corev1.RewardPoolBody{ + DeadlineBlockHeight: 100, + Action: &corev1.RewardPoolBody_SetAuthorities{SetAuthorities: &corev1.SetRewardPoolAuthorities{ + RewardsManagerPubkey: "DJj6F8oHLQM7Ec7FNh3sKWHJDZG7uH1zH7bPq3p1mUe2", + Authorities: []string{"0xnew"}, + }}, + } + sig, err := ProtoSign(priv, body) + if err != nil { + t.Fatalf("sign: %v", err) + } + got, err := ProtoRecover(body, sig) + if err != nil { + t.Fatalf("recover: %v", err) + } + if !strings.EqualFold(got, signer) { + t.Fatalf("recovered %q, want %q", got, signer) + } + }) +} + +// TestProtoSign_OneofTagDiscriminatesActions: two RewardPoolBodies — Create +// vs SetAuthorities — produce different signed bytes. The body's oneof field +// tag encodes which action variant is set, so even with otherwise-identical +// inner data the bytes (and thus signatures) diverge. +func TestProtoSign_OneofTagDiscriminatesActions(t *testing.T) { + priv, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("generate key: %v", err) + } + createBody := &corev1.RewardPoolBody{ + DeadlineBlockHeight: 1, + Action: &corev1.RewardPoolBody_Create{Create: &corev1.CreateRewardPool{Authorities: []string{"0xa"}}}, + } + setBody := &corev1.RewardPoolBody{ + DeadlineBlockHeight: 1, + Action: &corev1.RewardPoolBody_SetAuthorities{SetAuthorities: &corev1.SetRewardPoolAuthorities{RewardsManagerPubkey: "p", Authorities: []string{"0xa"}}}, + } + createSig, err := ProtoSign(priv, createBody) + if err != nil { + t.Fatalf("sign create: %v", err) + } + setSig, err := ProtoSign(priv, setBody) + if err != nil { + t.Fatalf("sign set: %v", err) + } + if createSig == setSig { + t.Fatalf("envelope-signed Create and SetAuthorities must differ; both are %q", createSig) + } +} + +// TestProtoSign_TamperingBreaksSignature: changing any field of the body +// (deadline, inner action data, etc.) after signing causes ProtoRecover to +// return a different (or no) signer. Replay with a tampered body fails. +func TestProtoSign_TamperingBreaksSignature(t *testing.T) { + priv, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("generate key: %v", err) + } + signer := crypto.PubkeyToAddress(priv.PublicKey).Hex() + + body := &corev1.RewardPoolBody{ + DeadlineBlockHeight: 100, + Action: &corev1.RewardPoolBody_SetAuthorities{SetAuthorities: &corev1.SetRewardPoolAuthorities{ + RewardsManagerPubkey: "DJj6F8oHLQM7Ec7FNh3sKWHJDZG7uH1zH7bPq3p1mUe2", + Authorities: []string{signer, "0xnew"}, + }}, + } + sig, err := ProtoSign(priv, body) + if err != nil { + t.Fatalf("sign: %v", err) + } + + // Tamper: extend the deadline. + tampered := &corev1.RewardPoolBody{ + DeadlineBlockHeight: 999_999, + Action: body.Action, + } + got, _ := ProtoRecover(tampered, sig) + if strings.EqualFold(got, signer) { + t.Fatalf("tampered deadline should not recover the original signer, got %q", got) + } +} diff --git a/pkg/common/reward_signing.go b/pkg/common/reward_signing.go deleted file mode 100644 index 7c0080f9..00000000 --- a/pkg/common/reward_signing.go +++ /dev/null @@ -1,69 +0,0 @@ -package common - -import ( - "crypto/ecdsa" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "sort" - - corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" -) - -// CreateDeterministicCreateRewardData creates deterministic hex data for signing CreateReward -func CreateDeterministicCreateRewardData(createReward *corev1.CreateReward) string { - // Sort claim authorities for deterministic ordering - authorities := make([]string, len(createReward.ClaimAuthorities)) - for i, auth := range createReward.ClaimAuthorities { - authorities[i] = fmt.Sprintf("%s:%s", auth.Address, auth.Name) - } - sort.Strings(authorities) - - authoritiesJson, _ := json.Marshal(authorities) - data := fmt.Sprintf("%s|%s|%d|%s|%d", - createReward.RewardId, - createReward.Name, - createReward.Amount, - string(authoritiesJson), - createReward.DeadlineBlockHeight) - - // Hash the data for consistent length - hash := sha256.Sum256([]byte(data)) - return hex.EncodeToString(hash[:]) -} - -// CreateDeterministicDeleteRewardData creates deterministic hex data for signing DeleteReward -func CreateDeterministicDeleteRewardData(deleteReward *corev1.DeleteReward) string { - data := fmt.Sprintf("%s|%d", deleteReward.Address, deleteReward.DeadlineBlockHeight) - - // Hash the data for consistent length - hash := sha256.Sum256([]byte(data)) - return hex.EncodeToString(hash[:]) -} - -// SignCreateReward signs a CreateReward message using deterministic data -func SignCreateReward(privateKey *ecdsa.PrivateKey, createReward *corev1.CreateReward) (string, error) { - signatureData := CreateDeterministicCreateRewardData(createReward) - - // Convert hex data to bytes for signing - dataBytes, err := hex.DecodeString(signatureData) - if err != nil { - return "", fmt.Errorf("invalid hex data: %w", err) - } - - return EthSign(privateKey, dataBytes) -} - -// SignDeleteReward signs a DeleteReward message using deterministic data -func SignDeleteReward(privateKey *ecdsa.PrivateKey, deleteReward *corev1.DeleteReward) (string, error) { - signatureData := CreateDeterministicDeleteRewardData(deleteReward) - - // Convert hex data to bytes for signing - dataBytes, err := hex.DecodeString(signatureData) - if err != nil { - return "", fmt.Errorf("invalid hex data: %w", err) - } - - return EthSign(privateKey, dataBytes) -} diff --git a/pkg/core/config/rewards.go b/pkg/core/config/rewards.go index bd522a3c..44dd12b1 100644 --- a/pkg/core/config/rewards.go +++ b/pkg/core/config/rewards.go @@ -2,6 +2,50 @@ package config import "github.com/OpenAudio/go-openaudio/pkg/rewards" +// Solana reward manager pubkeys for the AUDIO mint, per environment. +// +// AUDIO is intentionally outside the reward-pool primitive: pool-managed +// authority sets are for per-launchpad-coin rotation, while AUDIO continues +// to be governed by the network-wide validator/AAO trust set. The values +// here are used as a denylist by validateCreateRewardPool, which refuses +// to create a pool whose rewards_manager_pubkey matches the AUDIO RM — +// otherwise an attacker could create a pool for the AUDIO RM with their +// own keys as initial authorities and have validators sign AUDIO sender +// attestations on their behalf. +// +// Empty values disable the denylist for that environment (no enforcement). +// Sandbox / devnet typically don't have a real AUDIO RM and can be left +// empty; prod is required. +// +// Staging is intentionally left empty: staging doesn't run with a real +// AUDIO mint, and the rotation flow is exercised against test launchpad +// RMs that have first-class pools, so the denylist would never fire for +// any legitimate staging request. Setting a non-empty staging value +// would just be a misconfiguration risk with no upside. +var ( + DevAudioRewardsManagerPubkey = "DJPzVothq58SmkpRb1ATn5ddN2Rpv1j2TcGvM3XsHf1c" + StageAudioRewardsManagerPubkey = "" + ProdAudioRewardsManagerPubkey = "71hWFVYokLaN1PNYzTAWi13EfJ7Xt9VbSWUKsXUT8mxE" +) + +// AudioRewardsManagerPubkey returns the configured AUDIO RM pubkey for the +// current runtime environment, or "" if none is configured. Callers MUST +// treat "" as "no denylist enforcement" — they should not refuse to do work +// just because the constant is empty (the validator network was running +// before this denylist existed). +func AudioRewardsManagerPubkey() string { + switch GetRuntimeEnvironment() { + case "prod", "production", "mainnet": + return ProdAudioRewardsManagerPubkey + case "stage", "staging", "testnet": + return StageAudioRewardsManagerPubkey + case "dev", "development", "devnet", "local", "sandbox": + return DevAudioRewardsManagerPubkey + default: + return "" + } +} + var ( DevClaimAuthorities = []rewards.ClaimAuthority{ { diff --git a/pkg/core/db/models.go b/pkg/core/db/models.go index 1fb14caa..9806753b 100644 --- a/pkg/core/db/models.go +++ b/pkg/core/db/models.go @@ -247,19 +247,26 @@ type CoreResource struct { } type CoreReward struct { - ID int64 - Address string - Index int64 - TxHash string - Sender string - RewardID string - Name string - Amount int64 - ClaimAuthorities []string - RawMessage []byte - BlockHeight int64 - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + RawMessage []byte + BlockHeight int64 + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz + RewardsManagerPubkey pgtype.Text +} + +type CoreRewardPool struct { + RewardsManagerPubkey string + Authorities []string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } type CoreTransaction struct { @@ -305,6 +312,12 @@ type CoreValidator struct { Jailed bool } +type LaunchpadAuthorityRm struct { + Authority string + RewardsManagerPubkey string + CreatedAt pgtype.Timestamptz +} + type ManagementKey struct { ID int32 TrackID string diff --git a/pkg/core/db/reads.sql.go b/pkg/core/db/reads.sql.go index 41d54062..99e749f4 100644 --- a/pkg/core/db/reads.sql.go +++ b/pkg/core/db/reads.sql.go @@ -12,20 +12,41 @@ import ( ) const getActiveRewards = `-- name: GetActiveRewards :many -select id, address, index, tx_hash, sender, reward_id, name, amount, claim_authorities, raw_message, block_height, created_at, updated_at -from core_rewards -order by address -` - -func (q *Queries) GetActiveRewards(ctx context.Context) ([]CoreReward, error) { +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +order by r.address +` + +type GetActiveRewardsRow struct { + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + ClaimAuthorities []string + RawMessage []byte + BlockHeight int64 + RewardsManagerPubkey pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) GetActiveRewards(ctx context.Context) ([]GetActiveRewardsRow, error) { rows, err := q.db.Query(ctx, getActiveRewards) if err != nil { return nil, err } defer rows.Close() - var items []CoreReward + var items []GetActiveRewardsRow for rows.Next() { - var i CoreReward + var i GetActiveRewardsRow if err := rows.Scan( &i.ID, &i.Address, @@ -38,6 +59,7 @@ func (q *Queries) GetActiveRewards(ctx context.Context) ([]CoreReward, error) { &i.ClaimAuthorities, &i.RawMessage, &i.BlockHeight, + &i.RewardsManagerPubkey, &i.CreatedAt, &i.UpdatedAt, ); err != nil { @@ -215,23 +237,41 @@ func (q *Queries) GetAllRegisteredNodesSorted(ctx context.Context) ([]CoreValida } const getAllRewards = `-- name: GetAllRewards :many -select id, address, index, tx_hash, sender, reward_id, name, amount, claim_authorities, raw_message, block_height, created_at, updated_at from core_rewards -where address in ( - select distinct address - from core_rewards -) -order by block_height desc -` - -func (q *Queries) GetAllRewards(ctx context.Context) ([]CoreReward, error) { +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +order by r.block_height desc +` + +type GetAllRewardsRow struct { + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + ClaimAuthorities []string + RawMessage []byte + BlockHeight int64 + RewardsManagerPubkey pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) GetAllRewards(ctx context.Context) ([]GetAllRewardsRow, error) { rows, err := q.db.Query(ctx, getAllRewards) if err != nil { return nil, err } defer rows.Close() - var items []CoreReward + var items []GetAllRewardsRow for rows.Next() { - var i CoreReward + var i GetAllRewardsRow if err := rows.Scan( &i.ID, &i.Address, @@ -244,6 +284,7 @@ func (q *Queries) GetAllRewards(ctx context.Context) ([]CoreReward, error) { &i.ClaimAuthorities, &i.RawMessage, &i.BlockHeight, + &i.RewardsManagerPubkey, &i.CreatedAt, &i.UpdatedAt, ); err != nil { @@ -1442,6 +1483,30 @@ func (q *Queries) GetLatestSlaRollup(ctx context.Context) (SlaRollup, error) { return i, err } +const getLaunchpadRMByAuthority = `-- name: GetLaunchpadRMByAuthority :one +select rewards_manager_pubkey +from launchpad_authority_rm +where authority = any($1::text[]) +order by rewards_manager_pubkey, authority +limit 1 +` + +// Resolves a launchpad-derived per-mint claim authority (lowercased eth +// hex) to the Solana reward manager state account that mint's rewards +// live under. Used by PR2's wire-compat layer at block-sync replay time: +// when finalizeLegacyCreateReward sees an inline claim_authorities array, +// it looks up the RM from any one of its lowercased entries and routes +// the reward into a pool keyed by that RM — matching exactly what PR1's +// backfill produced for pre-migration rows. Returns ErrNoRows if none of +// the requested authorities is in the launchpad mapping (e.g., AUDIO +// rewards or test fixtures). +func (q *Queries) GetLaunchpadRMByAuthority(ctx context.Context, dollar_1 []string) (string, error) { + row := q.db.QueryRow(ctx, getLaunchpadRMByAuthority, dollar_1) + var rewards_manager_pubkey string + err := row.Scan(&rewards_manager_pubkey) + return rewards_manager_pubkey, err +} + const getMEAD = `-- name: GetMEAD :one select id, address, tx_hash, index, sender, resource_addresses, release_addresses, raw_message, raw_acknowledgment, block_height from core_mead where address = $1 order by block_height desc limit 1 ` @@ -2017,15 +2082,37 @@ func (q *Queries) GetRegisteredNodesByType(ctx context.Context, nodeType string) } const getReward = `-- name: GetReward :one -select id, address, index, tx_hash, sender, reward_id, name, amount, claim_authorities, raw_message, block_height, created_at, updated_at from core_rewards -where address = $1 -order by block_height desc +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where r.address = $1 +order by r.block_height desc limit 1 ` -func (q *Queries) GetReward(ctx context.Context, address string) (CoreReward, error) { +type GetRewardRow struct { + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + ClaimAuthorities []string + RawMessage []byte + BlockHeight int64 + RewardsManagerPubkey pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) GetReward(ctx context.Context, address string) (GetRewardRow, error) { row := q.db.QueryRow(ctx, getReward, address) - var i CoreReward + var i GetRewardRow err := row.Scan( &i.ID, &i.Address, @@ -2038,6 +2125,7 @@ func (q *Queries) GetReward(ctx context.Context, address string) (CoreReward, er &i.ClaimAuthorities, &i.RawMessage, &i.BlockHeight, + &i.RewardsManagerPubkey, &i.CreatedAt, &i.UpdatedAt, ) @@ -2045,15 +2133,37 @@ func (q *Queries) GetReward(ctx context.Context, address string) (CoreReward, er } const getRewardByID = `-- name: GetRewardByID :one -select id, address, index, tx_hash, sender, reward_id, name, amount, claim_authorities, raw_message, block_height, created_at, updated_at from core_rewards -where reward_id = $1 -order by block_height desc +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where r.reward_id = $1 +order by r.block_height desc limit 1 ` -func (q *Queries) GetRewardByID(ctx context.Context, rewardID string) (CoreReward, error) { +type GetRewardByIDRow struct { + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + ClaimAuthorities []string + RawMessage []byte + BlockHeight int64 + RewardsManagerPubkey pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) GetRewardByID(ctx context.Context, rewardID string) (GetRewardByIDRow, error) { row := q.db.QueryRow(ctx, getRewardByID, rewardID) - var i CoreReward + var i GetRewardByIDRow err := row.Scan( &i.ID, &i.Address, @@ -2066,6 +2176,7 @@ func (q *Queries) GetRewardByID(ctx context.Context, rewardID string) (CoreRewar &i.ClaimAuthorities, &i.RawMessage, &i.BlockHeight, + &i.RewardsManagerPubkey, &i.CreatedAt, &i.UpdatedAt, ) @@ -2073,15 +2184,37 @@ func (q *Queries) GetRewardByID(ctx context.Context, rewardID string) (CoreRewar } const getRewardByTxHash = `-- name: GetRewardByTxHash :one -select id, address, index, tx_hash, sender, reward_id, name, amount, claim_authorities, raw_message, block_height, created_at, updated_at from core_rewards -where tx_hash = $1 -order by block_height desc +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where r.tx_hash = $1 +order by r.block_height desc limit 1 ` -func (q *Queries) GetRewardByTxHash(ctx context.Context, txHash string) (CoreReward, error) { +type GetRewardByTxHashRow struct { + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + ClaimAuthorities []string + RawMessage []byte + BlockHeight int64 + RewardsManagerPubkey pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) GetRewardByTxHash(ctx context.Context, txHash string) (GetRewardByTxHashRow, error) { row := q.db.QueryRow(ctx, getRewardByTxHash, txHash) - var i CoreReward + var i GetRewardByTxHashRow err := row.Scan( &i.ID, &i.Address, @@ -2094,28 +2227,103 @@ func (q *Queries) GetRewardByTxHash(ctx context.Context, txHash string) (CoreRew &i.ClaimAuthorities, &i.RawMessage, &i.BlockHeight, + &i.RewardsManagerPubkey, &i.CreatedAt, &i.UpdatedAt, ) return i, err } -const getRewardsByClaimAuthority = `-- name: GetRewardsByClaimAuthority :many -select id, address, index, tx_hash, sender, reward_id, name, amount, claim_authorities, raw_message, block_height, created_at, updated_at -from core_rewards -where $1::text = any(claim_authorities) -order by address +const getRewardPool = `-- name: GetRewardPool :one +select rewards_manager_pubkey, authorities, created_at, updated_at +from core_reward_pools +where rewards_manager_pubkey = $1 +` + +func (q *Queries) GetRewardPool(ctx context.Context, rewardsManagerPubkey string) (CoreRewardPool, error) { + row := q.db.QueryRow(ctx, getRewardPool, rewardsManagerPubkey) + var i CoreRewardPool + err := row.Scan( + &i.RewardsManagerPubkey, + &i.Authorities, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getRewardPoolsByAuthority = `-- name: GetRewardPoolsByAuthority :many +select rewards_manager_pubkey, authorities, created_at, updated_at +from core_reward_pools +where authorities @> array[$1::text] +order by rewards_manager_pubkey ` -func (q *Queries) GetRewardsByClaimAuthority(ctx context.Context, dollar_1 string) ([]CoreReward, error) { +// Uses array containment (@>) so the gin index on authorities is used. +func (q *Queries) GetRewardPoolsByAuthority(ctx context.Context, dollar_1 string) ([]CoreRewardPool, error) { + rows, err := q.db.Query(ctx, getRewardPoolsByAuthority, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CoreRewardPool + for rows.Next() { + var i CoreRewardPool + if err := rows.Scan( + &i.RewardsManagerPubkey, + &i.Authorities, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRewardsByClaimAuthority = `-- name: GetRewardsByClaimAuthority :many +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where p.authorities @> array[$1::text] +order by r.address +` + +type GetRewardsByClaimAuthorityRow struct { + ID int64 + Address string + Index int64 + TxHash string + Sender string + RewardID string + Name string + Amount int64 + ClaimAuthorities []string + RawMessage []byte + BlockHeight int64 + RewardsManagerPubkey pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +// Uses array containment (@>) so the gin index on core_reward_pools.authorities +// can be used. = ANY(...) cannot leverage the gin opclass. +func (q *Queries) GetRewardsByClaimAuthority(ctx context.Context, dollar_1 string) ([]GetRewardsByClaimAuthorityRow, error) { rows, err := q.db.Query(ctx, getRewardsByClaimAuthority, dollar_1) if err != nil { return nil, err } defer rows.Close() - var items []CoreReward + var items []GetRewardsByClaimAuthorityRow for rows.Next() { - var i CoreReward + var i GetRewardsByClaimAuthorityRow if err := rows.Scan( &i.ID, &i.Address, @@ -2128,6 +2336,7 @@ func (q *Queries) GetRewardsByClaimAuthority(ctx context.Context, dollar_1 strin &i.ClaimAuthorities, &i.RawMessage, &i.BlockHeight, + &i.RewardsManagerPubkey, &i.CreatedAt, &i.UpdatedAt, ); err != nil { diff --git a/pkg/core/db/sql/migrations/00033_reward_pools.sql b/pkg/core/db/sql/migrations/00033_reward_pools.sql new file mode 100644 index 00000000..d3f4b671 --- /dev/null +++ b/pkg/core/db/sql/migrations/00033_reward_pools.sql @@ -0,0 +1,244 @@ +-- +migrate Up + +-- Reward pools group the eth addresses authorized to attest for rewards +-- under a specific Solana reward manager. The pool is identified BY the +-- reward manager pubkey (32-byte base58) — there is no separate "pool +-- address" concept. PR2's CreateRewardPool / SetRewardPoolAuthorities txs +-- mutate this table; PR3's sender-attestation gate consults it to decide +-- whether to sign add/delete attestations for a given (RM, eth address) +-- pair. +create table if not exists core_reward_pools ( + rewards_manager_pubkey text primary key, + authorities text[] not null default '{}', + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now() +); + +create index if not exists idx_core_reward_pools_authorities on core_reward_pools using gin (authorities); + +-- launchpad_authority_rm maps a launchpad-derived per-mint claim authority +-- (lowercased eth hex address) to the Solana reward manager state account +-- that mint's rewards live under. The values are produced by running +-- DeriveEthAddressForMint(domain="claimAuthority", secret, mint) for each +-- known launchpad mint and recording its RM. This table serves two +-- purposes: +-- +-- 1. The backfill block below uses it to find each existing reward row's +-- RM (by looking for any one of its claim_authorities that matches a +-- known launchpad authority). +-- 2. PR2's wire-compat layer (finalizeLegacyCreateReward) queries it at +-- block-sync replay time to produce the same RM-bound state the +-- migration produces, so historical replay and the migration agree +-- on the resulting apphash. +-- +-- The table must remain populated and queryable as long as any legacy- +-- format CreateReward txs may still be replayed during block sync. Future +-- launchpad mints can be added via additional migrations as they're +-- registered. +create table if not exists launchpad_authority_rm ( + authority text primary key, + rewards_manager_pubkey text not null, + created_at timestamp with time zone default now() +); + +insert into launchpad_authority_rm (authority, rewards_manager_pubkey) values + ('0x0c5b590a071a377c49a154639fd4343d147bcafe', 'urGNtpg4ppvLKH5Yvkmi1dixbTsokJdAdbzGs9iSSPy'), + ('0x103cf68f8d352ba3dbee8b56236befaa1e1429aa', 'BWC8rj4am5izbJXCpwyEfTxAo2StWGiwjBfSzAzKt4Gi'), + ('0x111d7c8cb8c46f743a7796a7f9c0f2fb08076f6f', '7hXopQnXfv8tRf8aVXvGauAHyUPcNKAT4gp49ufhxk36'), + ('0x1165e5f035ce5eeaed37c6ad190b58903bac6175', 'Di77DoUUbe8QDq6XU3jptxMLnT8S5kj8JZ3qGYRTHNKh'), + ('0x14035c457a724b83da6c88b7dca0d283d2789808', 'dWwazU2QUih1pDnuGWXfJnVG6uLfvogqnFknvrxju92'), + ('0x14637fad403350a1fdbd6d21cc7658ae46d7bbb0', '5z5TV5WhbUyhJDkhmeKsJLNgreVcNq3kNh3Kwd2mGHEE'), + ('0x16ac1f19e207794ff1462b2ffdad742b3b3c0ce7', 'Bvr2HYVpKThU4nCFhpDy7Y6XLBMZUuQHK1KRbAGnCiQt'), + ('0x1a17cf24fa74847c4c2e4e14d9fa5d55347ea829', 'FGrk4MQGFyjsgnCBCoCpRgMubo6SqSwvHYiEHuDnXrcE'), + ('0x1be07e9b5af6cad186f8e3771d97e11b3af27dbe', 'FDeFBV6XntX2LkkYFmBSyqhwf8w7MzvND3kcJWYD4gVa'), + ('0x1edf70aaf55952a8d9d75a550e0f7eef6593c0b9', 'GBDpJArbu7Mes5FnXYgMNAaUdayCAGRzpJrThaKr9dsS'), + ('0x212fbc84a052d73b9dbceae3c8de29597db64d0f', 'E7fZSM55CzM7gD9LcnN5jEakKJLcHUwyNQZc9RL2RgTT'), + ('0x21adafbd269728c08f2ee6d3a3a43455b7e09ad7', '7Lra25qHo7yJWTQjwAGcyK3PvHXLfKpqNZkpGX76dzkK'), + ('0x27ca8265e6d9f0a95657fa7d36964b10d826fa6d', 'BtU6seJeyf5b68mSA2iFH1wdipxNHWSuU9JGKhAYVtry'), + ('0x29063c045b421d064993bde6e98646b47998c4f7', 'CDZ6nAr8md5xz2Wvb3LdnLya6yjxpqcLTMZrb8zYMdcM'), + ('0x36d1b69132f70558dc4838d5ae87cca21c562f1f', '2QJhBYiEznpjgSSsezcccpfkYdngPik4fvQzxHMucm9L'), + ('0x3b3c30eb5dd9bb6f9b92a14448474242513299b2', 'GXX15br7UgnkhUskBtfTn26SsWbtjijofxGH5Be1LKqP'), + ('0x3e472953a85573bf25c657772a5f05624de5d9f1', 'FhHiVewd3dM63nJ6h3cZFdvMcyFdVJURhHUfYvvXDpQm'), + ('0x48f31a62c79ead300514cb890e68da88acc018e8', '3zaWKMBCR2mDoKCGjtyB4Y24xHQa1XdT2tGXuRThKg8U'), + ('0x4b797d0822d595de3e3ac3f2747bd5fdc2dfe96d', 'AvGcJBQpiWTvFFXW7GHYZXHpuNxjvGpZ8ezpjQXf2KN9'), + ('0x4e870de81a26d7e84dee34e8678b5958fe8730d1', '8CtDHsjPUpnGyNSAQpwGqPm46Np6jJWxRpzXH2277RX3'), + ('0x4fff490d410d5f412392bbad9e5b2b18742eea6c', '8DY6uaCyzmSXNneycc45tS1TxUTPjuYiFQYtsjWsASSN'), + ('0x503d34f7615b2a12651a54286f0d27d732647993', 'pRS2Gkt9YNoU1G9HAXeRruKGN5qRUQsg5pVxbtAuPmV'), + ('0x50dec0d97ee06a32c81651b8430ff424d6c58dc6', '4Aa5UrKqeUPrUVHRjiGAF6o1V3u17v2rQmERNEXLNkZ9'), + ('0x550d7cb8e314828c29bd48ce8e732e5799efe08d', 'GGrpfPa8gXPQ3FEusXhsPQBnWeNqb7Z1cfSD6gY3zunQ'), + ('0x5ad1a050b693f59eb64e4ac6d34bf30c3880cd24', 'BNJMsdNJN4NRJcisFtPbWF799eKa19YafLs6oDZAYDGm'), + ('0x5c78fe2d276450237d569bc4afa763bf9ccaf74f', 'GCrhftvMubAEQrnCcTUxLHxsCbnGr7wVdLtwjj1SkWQN'), + ('0x5f7f7dacbc1f64e28d465cb2644592963771d618', 'CBaP4ncJYxv2wmjT8bxtpuJ41wJU8SkbMKuaP4PP9S8C'), + ('0x66a9d306336e227a47a3486669bba023b141192d', '65EKqnYE8Rom3DnLGP7XbXjyNY8xXMfCnpjMZW14tsvV'), + ('0x6702d3bff4b3736cf72f9f64db4169a5853dbc9c', '83ENNoux1oKhMndc2Uw6BqwWTPHPavHWQhG292bZun3N'), + ('0x6b13053a2b4c7fc71d60d58dd55a97b34b245a80', '2WdvdUbEEY1XRYVQmrHFipTWg8Jy42nE4e7xFK3Q6gjo'), + ('0x6c6da83a8237ee4dc4ecfea880c17c4ed6d7a2c8', 'F3xcZ7jQFWBgp2kMjp9fHSkCehRWzuVND6n1mCu1oyjH'), + ('0x832d886079387f3ab825e978bfc682f6c158a3e8', '8FkZU5VooBFXqb8GA36kgTyHZ5J1ABqi13bBKzkExvBz'), + ('0x85f54509c6075ed7efe6889a85b713cb68ce56cf', '6UuCBYGutteDTYAhd1W5PpvvSAg4LEWiJYQnWVU79bER'), + ('0x868d17a61edafd0b9d96032cd44570a95ff6a04f', '86jE4ubrbFGwdwTv6iN2FZpQm2EiHXru4teowrYpu8Tn'), + ('0x86a60f5b2b3f29a42c80fec03818bd329655187f', '4kmzFHSSrUxeTkfRrYmKv96o7guqi7LPBaZrzqvKQYL5'), + ('0x8719de0df16a3e0889d8509b351dafabee4ea294', 'GaDBQ27F7EmNHumchE6Pjr43frckBuxsUUJjN45uAajQ'), + ('0x8ad3ca46321b18e06b35d4c8e76d18d976de7727', 'GQxHiQFfeby9wfyUw5Bw1Q1xPB7ZqwgKjB36PtHhKstV'), + ('0x8f4458d8f0ae1b3f83e84be178fd1c4f3fc36bd4', 'CAQm1eMbUn3j5qhFWT4FmnDzwuNddy1supzR5A85VVfW'), + ('0x90aaffd8c2bbb0b68e99b90319c3c7bb43d33eb2', 'mY3V2Y2Trjsaa3qHrrL7s3aNWEG6getJUdpXvf71uqe'), + ('0x9293d36202216e47afb9d7762f05ad52c59403e4', 'AdDEp4rJe7RAFRg14uaFMUmPfLHJ6oJeSe49aXSwPVgZ'), + ('0x9d7ba5f04c7dc2993ff2ccdf19e29ad741036e90', '78XW1jt6T5mXTBocoDuTqB4RAM3mAKnYGSYWDeXY135s'), + ('0x9f3ebdf813c04ef8096326dd134270b376525e1a', 'fWcM3QvikFoZGKQ9eJ8yZ4Q7SVcHydnGMoR7fdrtza9'), + ('0xa1ef8ef283501d456b76757b32ea345cdcb4224f', '8u3ejuKz7uywPqdz5fS8cB4ppXvAZ3WiLeVKdiFWoK2r'), + ('0xa29dff72f7aecb95d1414f7206eb69820ec86cc7', '2hcSdCmtCfgNhjBELh6a9GJ2WSbbU8LMMonqFLZTAp67'), + ('0xa85281a327c0848a619659391eb88dd3702fcf41', '781STZvPpSTaDNcB35tbAWk2fwbTpqaRNRpSQpun7C3X'), + ('0xaa49c2afad744ac3595baed0d1f9c7876720c9df', 'EUqRiCAgj7QnP176ThaGfrGz3r2VxxNyXuAPU5SPhMJS'), + ('0xad6b9555369a438fb4698c46dfad245ef97b270e', '8jrWskYgwYPEZHSUCKnQmdq4hGKgAt9gcZeXtQCsRnJs'), + ('0xb3f3f879c7c0cb0a4af1fcddb6557fc2b3963bc9', '3Z4GXG84TjmvnV8h16qraFmWeEBv2eQd9quc39UhwiAe'), + ('0xb61525df350dbfc6a2ac82412f6ece0cc4dbd7ff', 'CFawCKFwxqLejDdoyUMjyBQokX2n37E37oS8HAUogmAM'), + ('0xbbecbfecf49bb3159c67fa5d254c40307c6fce2b', '86Wqf14Hj3DQMwgpuqQtAApJT3Wyf4wR7wWmdYzfkLWZ'), + ('0xbc731ab6905ef383b519702dffbe8bb3f9ccf547', 'EgTvyvn9QDsAyXVamM8pzV2YHoVRuWBVmRFD56BaGgkS'), + ('0xbd00af91f78a651bdb74892f7c7207ffed7ef3dd', 'A3ZhYjqJfBZtJxUGXXruYHSWR5Zf7NaBgteTwEhrKwow'), + ('0xc402938db359d2644df952697dc3925c62fef99e', 'B4ijMf1byKLmRUV9RZaDCZqs4TTPGEfbEYD67B5M22Z2'), + ('0xc82c810ae45ba45d5ccdc02273f48e453d51e2f9', '6YAc7rLW8uom3DRnj6wkwvq6qpW9PkAfxBxXpU2wyyph'), + ('0xc8acdb3049903f6758c434d21b77af2b8af48d9e', 'ELs98dgSh3Kd6ViMM9LFT1rfEbPvm4e6xpQzjK2wVjH6'), + ('0xce61db54629ef47d0dfb2923660c787ac92fcec1', 'P76iaELTfBg1hKo6hr22scupEMhDsAcayg5AMGpQiCM'), + ('0xcece39acb112bfbc59fe41606d4475435f1b3676', '4E7B4BGd2bfjutdjkWsMcjgubGkxgf5niNNuKQKhbfpQ'), + ('0xda1a09589098954b05648419cc4bbea20c6e39ea', '46w1RsTcygL5C6ijfaAztUstPqHuzZXPEB2KCr52cYHf'), + ('0xdaef8d116025a7053577c95cf793827cbf6d7767', '7ij5BsYE2aL3QG2Mt1ifB3mNZx9ELXBEZbCMX6nLM977'), + ('0xe325971fe787077e8b6d42e9f353eea06a801680', '31Dq71KBhKEs2nEwytX8RwEC1wBS2pUaQdPVk3zTHsWP'), + ('0xe9448fb249fb675f6d51483edef063bf64487a3f', 'GqHmpeQkqNW7h8mPUQPsXqy3E5CVy1siMbeHuU9uhNVi'), + ('0xee878a78a703a67c73d1e794e61bb36fc715eef2', '6TW5NzdMF7ps4zaDo6ogCV1iUHde4VdA2ZyNtJKLwSLp'), + ('0xf0ed91252a509ff2d24f0dd6e22c0e985f636334', 'BrY2VSnagcjsr6DND1FpcfpYiJ8PrBdheuPRggKVfZUN'), + ('0xf174de7442c805c3900ce4140a81f329c5a7f5bc', '3G9yANcxNcFobZgZbP3TyQuF4zqC3hqeqEi1LDcJjJgo'), + ('0xf3baf2379bb060fa63f620576fbb977ef5fa4e51', '8dZLVViKogvR8nPFVLcK64r32WG5GKZojn2BTvJ2LHHL'), + ('0xfa29072ea2933a8ece9842d19480184ef9583ba8', '8ThSmVGV6NW36JDuVgNCgYuh6b9QycaQCmRT18boS4aR'), + ('0xfe3d5e7b8bab7599d63f43dcd673f7c27424296e', 'G1YQE4itykWEWSheCzKMKVma7D7AYxrQ7UY92DvZLUeV'), + ('0xffbb95b5631c39e860016d23918592489031248c', '454bPX92ayZf6XmuGRjFGKGAr43hYX2T8U3avcd6KNGi') +on conflict (authority) do nothing; + +-- Add rewards_manager_pubkey FK to core_rewards. Nullable: rows whose +-- claim_authorities don't include any known launchpad-derived authority +-- (e.g., test fixtures, abandoned rewards) stay NULL after the backfill +-- and have no pool. PR3's sender-attestation gate refuses to operate on +-- NULL-RM rewards (no pool, no authority data). +alter table core_rewards add column if not exists rewards_manager_pubkey text; +create index if not exists idx_core_rewards_rewards_manager_pubkey on core_rewards (rewards_manager_pubkey); + +-- Backfill step 1: insert one core_reward_pools row per (RM, canonical +-- authorities) pair seen in core_rewards. The pool's RM is determined by +-- finding any one of the row's claim_authorities that matches a launchpad +-- authority — so each unique authority set becomes a real RM-bound pool, +-- not a synthetic mig_ identifier. The pool's authorities array is +-- the canonical (trim, lower, dedup, sort) form of the row's full +-- claim_authorities, preserving the partner-signer / leaked-key entries +-- as the pool's *initial* authorities. PR2's SetRewardPoolAuthorities is +-- the rotation surface that drains them out. +-- Per-RM authority union: for each RM, take the UNION of authorities +-- across every reward whose claim_authorities contain ANY of that RM's +-- launchpad-derived keys. This guards against the case where two +-- rewards under the same mint were created with slightly different +-- authority sets (e.g., one had an additional debug key) — a naive +-- "ON CONFLICT DO NOTHING" insert would keep an arbitrary +-- first-inserted set and silently drop authorities present on other +-- rewards. The union preserves all of them as the pool's initial set; +-- SetRewardPoolAuthorities is the rotation surface that drains them. +insert into core_reward_pools (rewards_manager_pubkey, authorities) +select + rm.rewards_manager_pubkey, + array( + select distinct lower(trim(a)) + from core_rewards r, + unnest(r.claim_authorities) as a + where r.claim_authorities is not null + and array_length(r.claim_authorities, 1) > 0 + and a is not null + and trim(a) <> '' + and exists ( + select 1 + from unnest(r.claim_authorities) as r_auth + where lower(trim(r_auth)) = rm.authority + ) + order by 1 + ) as authorities +from launchpad_authority_rm rm +where exists ( + select 1 + from core_rewards r, + unnest(r.claim_authorities) as r_auth + where r.claim_authorities is not null + and array_length(r.claim_authorities, 1) > 0 + and lower(trim(r_auth)) = rm.authority +) +on conflict (rewards_manager_pubkey) do update set + authorities = excluded.authorities, + updated_at = now(); + +-- Backfill step 2: point each core_rewards row at its pool. We pick the RM +-- from any launchpad-derived authority found among the row's +-- claim_authorities. In practice each row should contain exactly one +-- launchpad authority (the per-mint key for the coin the reward was issued +-- under), so the lateral lookup yields a unique RM; rows with no matching +-- authority are left with NULL rewards_manager_pubkey. +update core_rewards r +set rewards_manager_pubkey = mapped.rewards_manager_pubkey +from ( + select + r2.address as reward_address, + rm.rewards_manager_pubkey + from core_rewards r2 + cross join lateral ( + select rm.rewards_manager_pubkey + from launchpad_authority_rm rm + where rm.authority = any ( + select lower(trim(a)) from unnest(r2.claim_authorities) a + where a is not null and trim(a) <> '' + ) + order by rm.rewards_manager_pubkey, rm.authority + limit 1 + ) rm + where r2.claim_authorities is not null + and array_length(r2.claim_authorities, 1) > 0 +) mapped +where r.address = mapped.reward_address; + +-- Rows whose claim_authorities don't include any launchpad-mapped per-mint +-- key are intentionally left with NULL rewards_manager_pubkey and have no +-- pool. The launchpad_authority_rm seed above is the complete set of RM +-- init events; rows that don't match are stale fixture data, never live +-- production rewards. Such rewards are unclaimable post-migration; their +-- claim authority recovers by submitting CreateRewardPool + a fresh +-- CreateReward via PR2's transactions. + +alter table core_rewards + add constraint fk_core_rewards_rewards_manager_pubkey + foreign key (rewards_manager_pubkey) references core_reward_pools(rewards_manager_pubkey) + on delete cascade; + +-- The row's rewards_manager_pubkey now carries the authorization signal; +-- the inline claim_authorities column and its gin index are redundant. +drop index if exists idx_core_rewards_claim_authorities; +alter table core_rewards drop column if exists claim_authorities; + +-- +migrate Down + +-- Restore the column (backfilled from the pool's authorities) before tearing +-- down the FK so existing data is preserved on a downgrade. +-- +-- KNOWN LIMITATION: rewards whose claim_authorities did not include any +-- launchpad-derived authority were left with NULL rewards_manager_pubkey +-- by the Up migration (no pool was created for them). Those rows have no +-- pool to restore from, so the JOIN below leaves their claim_authorities +-- as the column default ('{}'). The original inline claim_authorities are +-- not recoverable on a downgrade. This is acceptable because the Down +-- migration is a best-effort rollback path — production rollback would +-- happen at the chain level, not via SQL Down. +alter table core_rewards add column if not exists claim_authorities text[] default '{}'; +update core_rewards r +set claim_authorities = coalesce(p.authorities, '{}'::text[]) +from core_reward_pools p +where p.rewards_manager_pubkey = r.rewards_manager_pubkey; +create index if not exists idx_core_rewards_claim_authorities on core_rewards using gin (claim_authorities); + +alter table core_rewards drop constraint if exists fk_core_rewards_rewards_manager_pubkey; +drop index if exists idx_core_rewards_rewards_manager_pubkey; +alter table core_rewards drop column if exists rewards_manager_pubkey; + +drop index if exists idx_core_reward_pools_authorities; +drop table if exists core_reward_pools; +drop table if exists launchpad_authority_rm; diff --git a/pkg/core/db/sql/reads.sql b/pkg/core/db/sql/reads.sql index edeeb88d..0b0e20b5 100644 --- a/pkg/core/db/sql/reads.sql +++ b/pkg/core/db/sql/reads.sql @@ -586,41 +586,95 @@ where b.height = $1 order by b.height, t.index asc; -- name: GetReward :one -select * from core_rewards -where address = $1 -order by block_height desc +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where r.address = $1 +order by r.block_height desc limit 1; -- name: GetRewardByID :one -select * from core_rewards -where reward_id = $1 -order by block_height desc +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where r.reward_id = $1 +order by r.block_height desc limit 1; -- name: GetRewardByTxHash :one -select * from core_rewards -where tx_hash = $1 -order by block_height desc +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where r.tx_hash = $1 +order by r.block_height desc limit 1; -- name: GetAllRewards :many -select * from core_rewards -where address in ( - select distinct address - from core_rewards -) -order by block_height desc; +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +order by r.block_height desc; -- name: GetActiveRewards :many -select * -from core_rewards -order by address; +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +left join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +order by r.address; -- name: GetRewardsByClaimAuthority :many -select * -from core_rewards -where $1::text = any(claim_authorities) -order by address; +-- Uses array containment (@>) so the gin index on core_reward_pools.authorities +-- can be used. = ANY(...) cannot leverage the gin opclass. +select + r.id, r.address, r.index, r.tx_hash, r.sender, r.reward_id, r.name, r.amount, + coalesce(p.authorities, '{}'::text[])::text[] as claim_authorities, + r.raw_message, r.block_height, r.rewards_manager_pubkey, r.created_at, r.updated_at +from core_rewards r +join core_reward_pools p on p.rewards_manager_pubkey = r.rewards_manager_pubkey +where p.authorities @> array[$1::text] +order by r.address; + +-- name: GetRewardPool :one +select rewards_manager_pubkey, authorities, created_at, updated_at +from core_reward_pools +where rewards_manager_pubkey = $1; + +-- name: GetRewardPoolsByAuthority :many +-- Uses array containment (@>) so the gin index on authorities is used. +select rewards_manager_pubkey, authorities, created_at, updated_at +from core_reward_pools +where authorities @> array[$1::text] +order by rewards_manager_pubkey; + +-- name: GetLaunchpadRMByAuthority :one +-- Resolves a launchpad-derived per-mint claim authority (lowercased eth +-- hex) to the Solana reward manager state account that mint's rewards +-- live under. Used by PR2's wire-compat layer at block-sync replay time: +-- when finalizeLegacyCreateReward sees an inline claim_authorities array, +-- it looks up the RM from any one of its lowercased entries and routes +-- the reward into a pool keyed by that RM — matching exactly what PR1's +-- backfill produced for pre-migration rows. Returns ErrNoRows if none of +-- the requested authorities is in the launchpad mapping (e.g., AUDIO +-- rewards or test fixtures). +select rewards_manager_pubkey +from launchpad_authority_rm +where authority = any($1::text[]) +order by rewards_manager_pubkey, authority +limit 1; -- name: GetCoreUpload :one select * from core_uploads where cid = $1 OR transcoded_cid = $1; diff --git a/pkg/core/db/sql/writes.sql b/pkg/core/db/sql/writes.sql index 26a01f05..77ea26b7 100644 --- a/pkg/core/db/sql/writes.sql +++ b/pkg/core/db/sql/writes.sql @@ -337,7 +337,7 @@ insert into core_rewards ( reward_id, name, amount, - claim_authorities, + rewards_manager_pubkey, raw_message, block_height ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); @@ -346,7 +346,7 @@ insert into core_rewards ( update core_rewards set name = $2, amount = $3, - claim_authorities = $4, + rewards_manager_pubkey = $4, raw_message = $5, block_height = $6, updated_at = now() @@ -356,6 +356,41 @@ where address = $1; delete from core_rewards where address = $1; +-- name: UpsertSyntheticRewardPool :exec +-- Used by the legacy CreateReward path to ensure a reward-pool row exists +-- for the provided rewards_manager_pubkey with the requested authority +-- set. The pubkey may be a real Solana RM (when the row's +-- claim_authorities included a known launchpad-derived per-mint key) or +-- a synthetic 'mig_' identifier (otherwise). DO UPDATE refreshes +-- the stored authorities so multiple CreateReward txs targeting the +-- same pool converge instead of leaving stale rows. +insert into core_reward_pools ( + rewards_manager_pubkey, + authorities +) values ($1, $2) +on conflict (rewards_manager_pubkey) do update set + authorities = excluded.authorities, + updated_at = now(); + +-- name: InsertRewardPool :exec +-- Inserts a first-class reward pool created via a CreateRewardPool cometbft +-- transaction. The pool's identity IS the Solana reward manager pubkey; +-- uniqueness is validated at validate time and same-block collisions +-- surface here as a PK violation that fails the second tx in the block. +insert into core_reward_pools ( + rewards_manager_pubkey, + authorities +) values ($1, $2); + +-- name: UpdateRewardPoolAuthorities :exec +-- Replaces the authority set on an existing pool wholesale. Used by the +-- SetRewardPoolAuthorities finalizer; callers compose the new list themselves +-- (current minus the removed key, current plus the added key, etc.). +update core_reward_pools +set authorities = $2, + updated_at = now() +where rewards_manager_pubkey = $1; + -- name: InsertFileUpload :exec insert into core_uploads( uploader_address, diff --git a/pkg/core/db/writes.sql.go b/pkg/core/db/writes.sql.go index 84cfdb01..bf57627b 100644 --- a/pkg/core/db/writes.sql.go +++ b/pkg/core/db/writes.sql.go @@ -407,23 +407,23 @@ insert into core_rewards ( reward_id, name, amount, - claim_authorities, + rewards_manager_pubkey, raw_message, block_height ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ` type InsertCoreRewardParams struct { - Address string - TxHash string - Index int64 - Sender string - RewardID string - Name string - Amount int64 - ClaimAuthorities []string - RawMessage []byte - BlockHeight int64 + Address string + TxHash string + Index int64 + Sender string + RewardID string + Name string + Amount int64 + RewardsManagerPubkey pgtype.Text + RawMessage []byte + BlockHeight int64 } func (q *Queries) InsertCoreReward(ctx context.Context, arg InsertCoreRewardParams) error { @@ -435,7 +435,7 @@ func (q *Queries) InsertCoreReward(ctx context.Context, arg InsertCoreRewardPara arg.RewardID, arg.Name, arg.Amount, - arg.ClaimAuthorities, + arg.RewardsManagerPubkey, arg.RawMessage, arg.BlockHeight, ) @@ -891,6 +891,27 @@ func (q *Queries) InsertRegisteredNode(ctx context.Context, arg InsertRegistered return err } +const insertRewardPool = `-- name: InsertRewardPool :exec +insert into core_reward_pools ( + rewards_manager_pubkey, + authorities +) values ($1, $2) +` + +type InsertRewardPoolParams struct { + RewardsManagerPubkey string + Authorities []string +} + +// Inserts a first-class reward pool created via a CreateRewardPool cometbft +// transaction. The pool's identity IS the Solana reward manager pubkey; +// uniqueness is validated at validate time and same-block collisions +// surface here as a PK violation that fails the second tx in the block. +func (q *Queries) InsertRewardPool(ctx context.Context, arg InsertRewardPoolParams) error { + _, err := q.db.Exec(ctx, insertRewardPool, arg.RewardsManagerPubkey, arg.Authorities) + return err +} + const insertSoundRecording = `-- name: InsertSoundRecording :exec insert into sound_recordings (sound_recording_id, track_id, cid, encoding_details) values ($1, $2, $3, $4) @@ -1054,7 +1075,7 @@ const updateCoreReward = `-- name: UpdateCoreReward :exec update core_rewards set name = $2, amount = $3, - claim_authorities = $4, + rewards_manager_pubkey = $4, raw_message = $5, block_height = $6, updated_at = now() @@ -1062,12 +1083,12 @@ where address = $1 ` type UpdateCoreRewardParams struct { - Address string - Name string - Amount int64 - ClaimAuthorities []string - RawMessage []byte - BlockHeight int64 + Address string + Name string + Amount int64 + RewardsManagerPubkey pgtype.Text + RawMessage []byte + BlockHeight int64 } func (q *Queries) UpdateCoreReward(ctx context.Context, arg UpdateCoreRewardParams) error { @@ -1075,13 +1096,33 @@ func (q *Queries) UpdateCoreReward(ctx context.Context, arg UpdateCoreRewardPara arg.Address, arg.Name, arg.Amount, - arg.ClaimAuthorities, + arg.RewardsManagerPubkey, arg.RawMessage, arg.BlockHeight, ) return err } +const updateRewardPoolAuthorities = `-- name: UpdateRewardPoolAuthorities :exec +update core_reward_pools +set authorities = $2, + updated_at = now() +where rewards_manager_pubkey = $1 +` + +type UpdateRewardPoolAuthoritiesParams struct { + RewardsManagerPubkey string + Authorities []string +} + +// Replaces the authority set on an existing pool wholesale. Used by the +// SetRewardPoolAuthorities finalizer; callers compose the new list themselves +// (current minus the removed key, current plus the added key, etc.). +func (q *Queries) UpdateRewardPoolAuthorities(ctx context.Context, arg UpdateRewardPoolAuthoritiesParams) error { + _, err := q.db.Exec(ctx, updateRewardPoolAuthorities, arg.RewardsManagerPubkey, arg.Authorities) + return err +} + const updateStorageProof = `-- name: UpdateStorageProof :exec update storage_proofs set proof = $1, status = $2 @@ -1136,3 +1177,30 @@ func (q *Queries) UpsertSlaRollupReport(ctx context.Context, address string) err _, err := q.db.Exec(ctx, upsertSlaRollupReport, address) return err } + +const upsertSyntheticRewardPool = `-- name: UpsertSyntheticRewardPool :exec +insert into core_reward_pools ( + rewards_manager_pubkey, + authorities +) values ($1, $2) +on conflict (rewards_manager_pubkey) do update set + authorities = excluded.authorities, + updated_at = now() +` + +type UpsertSyntheticRewardPoolParams struct { + RewardsManagerPubkey string + Authorities []string +} + +// Used by the legacy CreateReward path to ensure a reward-pool row exists +// for the provided rewards_manager_pubkey with the requested authority +// set. The pubkey may be a real Solana RM (when the row's +// claim_authorities included a known launchpad-derived per-mint key) or +// a synthetic 'mig_' identifier (otherwise). DO UPDATE refreshes +// the stored authorities so multiple CreateReward txs targeting the +// same pool converge instead of leaving stale rows. +func (q *Queries) UpsertSyntheticRewardPool(ctx context.Context, arg UpsertSyntheticRewardPoolParams) error { + _, err := q.db.Exec(ctx, upsertSyntheticRewardPool, arg.RewardsManagerPubkey, arg.Authorities) + return err +} diff --git a/pkg/core/server/abci.go b/pkg/core/server/abci.go index c8f3b457..72a2d010 100644 --- a/pkg/core/server/abci.go +++ b/pkg/core/server/abci.go @@ -1020,6 +1020,11 @@ func (s *Server) validateBlockTx(ctx context.Context, blockTime time.Time, block s.logger.Error("Invalid block: invalid file upload tx", zap.Error(err)) return false, nil } + case *v1.SignedTransaction_RewardPool: + if err := s.isValidRewardPoolTransaction(ctx, signedTx, blockHeight); err != nil { + s.logger.Error("Invalid block: invalid reward pool tx", zap.Error(err)) + return false, nil + } } return true, nil } @@ -1028,6 +1033,8 @@ func (s *Server) validateV1Transaction(ctx context.Context, currentHeight int64, switch signedTx.Transaction.(type) { case *v1.SignedTransaction_Reward: return s.isValidRewardTransaction(ctx, signedTx, currentHeight) + case *v1.SignedTransaction_RewardPool: + return s.isValidRewardPoolTransaction(ctx, signedTx, currentHeight) default: // For other transaction types, no validation needed during SendTransaction return nil @@ -1059,6 +1066,8 @@ func (s *Server) finalizeTransaction(ctx context.Context, req *abcitypes.Finaliz return s.finalizeRelease(ctx, msg, txHash) case *v1.SignedTransaction_Reward: return s.finalizeRewardTransaction(ctx, req, msg.GetReward(), txHash, sender) + case *v1.SignedTransaction_RewardPool: + return s.finalizeRewardPoolTransaction(ctx, req, msg.GetRewardPool(), txHash, 0) case *v1.SignedTransaction_FileUpload: return s.finalizeFileUpload(ctx, msg, txHash, req.Height) default: diff --git a/pkg/core/server/connect.go b/pkg/core/server/connect.go index 2f15f4e9..182dbb33 100644 --- a/pkg/core/server/connect.go +++ b/pkg/core/server/connect.go @@ -24,6 +24,7 @@ import ( storagev1connect "github.com/OpenAudio/go-openaudio/pkg/api/storage/v1/v1connect" "github.com/OpenAudio/go-openaudio/pkg/common" "github.com/OpenAudio/go-openaudio/pkg/core/config" + "github.com/OpenAudio/go-openaudio/pkg/core/db" "github.com/OpenAudio/go-openaudio/pkg/mediorum/server/signature" "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/jackc/pgx/v5" @@ -1060,16 +1061,119 @@ func (c *CoreService) GetStatus(ctx context.Context, _ *connect.Request[v1.GetSt // GetRewardAttestation implements v1connect.CoreServiceHandler. // -// TODO: temporarily disabled. Restore the programmatic-reward attestation -// flow (see git history for the previous implementation) once artist-coin -// attestations are ready to be re-enabled. +// Restored from the temporary kill-switch (#215) now that the rotation +// primitive is in place. The authority check uses dbReward.ClaimAuthorities, +// which post-PR1 is sourced from coalesce(p.authorities, '{}') via the +// LEFT JOIN on core_reward_pools — i.e., it reflects the current pool +// membership for the reward's RM, not the stale row-frozen list. So +// rotating an authority out via SetRewardPoolAuthorities immediately +// revokes their ability to authenticate claim attestations. func (c *CoreService) GetRewardAttestation(ctx context.Context, req *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) { - return nil, connect.NewError(connect.CodeFailedPrecondition, errors.New("artist-coin attestations are temporarily disabled")) + ethRecipientAddress := req.Msg.EthRecipientAddress + if ethRecipientAddress == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("eth_recipient_address is required")) + } + rewardAddress := req.Msg.RewardAddress + if rewardAddress == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward_address is required")) + } + specifier := req.Msg.Specifier + if specifier == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("specifier is required")) + } + if len(specifier) > 256 { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("specifier too long")) + } + claimAuthority := req.Msg.ClaimAuthority + if claimAuthority == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("claim_authority is required")) + } + signature := req.Msg.Signature + if signature == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("signature is required")) + } + amount := req.Msg.Amount + if amount == 0 { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("amount is required")) + } + rewardId := req.Msg.RewardId + if rewardId == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward_id is required")) + } + if req.Msg.AmountDecimals > 18 { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("amount_decimals too large; max 18")) + } + + dbReward, err := c.core.db.GetReward(ctx, rewardAddress) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, connect.NewError(connect.CodeNotFound, errors.New("programmatic reward not found")) + } + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to load reward: %w", err)) + } + + // dbReward.ClaimAuthorities is fed by the pool's current authorities + // (post-PR1 LEFT JOIN aliasing). Building a Reward from it gives us + // pool-gated authentication for free. + claimAuthorities := make([]rewards.ClaimAuthority, len(dbReward.ClaimAuthorities)) + for i, ca := range dbReward.ClaimAuthorities { + claimAuthorities[i] = rewards.ClaimAuthority{Address: ca} + } + reward := rewards.Reward{ + ClaimAuthorities: claimAuthorities, + Amount: uint64(dbReward.Amount), + RewardId: dbReward.RewardID, + Name: dbReward.Name, + } + + // RewardAddress is intentionally NOT set here, even though the proto + // field exists and RewardClaim.Compile supports a 3-piece + // RewardAddress:RewardID:Specifier disbursement_id form. The bytes + // produced by Compile are exactly what the Solana reward manager + // program reconstructs and verifies during evaluate_attestations, + // and that program expects the 2-piece RewardID:Specifier form. + // Adding RewardAddress here would break on-chain signature + // verification, not just change the validator-side signing contract. + // Cross-reward replay protection therefore relies on Specifier being + // disbursement-unique (per recipient + per event), which is the + // existing contract — not on the address binding. + claim := rewards.RewardClaim{ + RecipientEthAddress: ethRecipientAddress, + Amount: amount, + RewardID: req.Msg.RewardId, + Specifier: specifier, + ClaimAuthority: claimAuthority, + Decimals: req.Msg.AmountDecimals, + } + + attester := rewards.NewRewardAttester(c.core.config.EthereumKey, []rewards.Reward{reward}) + + if err := attester.Validate(claim); err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("claim validation failed: %w", err)) + } + if err := attester.Authenticate(claim, signature); err != nil { + return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("authentication failed: %w", err)) + } + _, attestation, err := attester.Attest(claim) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("attestation generation failed: %w", err)) + } + + return connect.NewResponse(&v1.GetRewardAttestationResponse{ + Owner: attester.EthereumAddress, + Attestation: attestation, + }), nil } // GetRewards implements v1connect.CoreServiceHandler. func (c *CoreService) GetRewards(ctx context.Context, req *connect.Request[v1.GetRewardsRequest]) (*connect.Response[v1.GetRewardsResponse], error) { - claimAuthority := req.Msg.ClaimAuthority + // Stored authorities are lowercased by rewards.CanonicalAuthorities; the + // underlying GetRewardsByClaimAuthority does a case-sensitive @> array + // containment check. Normalize the caller-supplied address (which is + // often checksum-case from common.PrivKeyToAddress) so lookups match. + // Normalize before the empty check so whitespace-only input is rejected + // as InvalidArgument rather than silently producing an empty result. + claimAuthority := strings.ToLower(strings.TrimSpace(req.Msg.ClaimAuthority)) if claimAuthority == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("claim_authority required")) } @@ -1152,6 +1256,34 @@ func (c *CoreService) GetReward(ctx context.Context, req *connect.Request[v1.Get return nil, connect.NewError(connect.CodeNotFound, nil) } +// GetRewardPool returns a reward pool keyed by its Solana reward manager +// pubkey. Every pool in core_reward_pools is RM-bound (PR1's backfill +// resolves each existing reward to a real RM via the launchpad mapping; +// CreateRewardPool validates that new pools use a base58 32-byte pubkey). +// There is no separate synthetic-pool surface to filter out. +func (c *CoreService) GetRewardPool(ctx context.Context, req *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) { + // Normalize and shape-validate up front so malformed input returns + // InvalidArgument deterministically instead of falling through to a DB + // lookup that returns NotFound. Reuses validateRewardsManagerPubkey + // (the same validator the CreateRewardPool / SetRewardPoolAuthorities + // tx path uses) so the contract is identical across read and write. + rewardsManagerPubkey := strings.TrimSpace(req.Msg.RewardsManagerPubkey) + if err := validateRewardsManagerPubkey(rewardsManagerPubkey); err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + pool, err := c.core.db.GetRewardPool(ctx, rewardsManagerPubkey) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("pool not found for rewards_manager_pubkey: %s", rewardsManagerPubkey)) + } + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get pool: %w", err)) + } + return connect.NewResponse(&v1.GetRewardPoolResponse{ + RewardsManagerPubkey: pool.RewardsManagerPubkey, + Authorities: pool.Authorities, + }), nil +} + // GetERN implements v1connect.CoreServiceHandler. func (c *CoreService) GetERN(ctx context.Context, req *connect.Request[v1.GetERNRequest]) (*connect.Response[v1.GetERNResponse], error) { address := req.Msg.Address @@ -1637,12 +1769,16 @@ func (c *CoreService) GetSlashAttestations(ctx context.Context, req *connect.Req // gatherEligibleSenderAddresses returns the union of registered validator // eth addresses and anti-abuse oracle eth addresses. This is the set the -// rewards-manager program treats as eligible senders: add attestations are -// only signed for addresses in this set, delete attestations are only signed -// for addresses NOT in this set. +// rewards-manager program treats as eligible senders for RMs that are NOT +// managed by the pool primitive: add attestations are only signed for +// addresses in this set, delete attestations are only signed for addresses +// NOT in this set. // // Validators come from the local consensus-populated core_validators table. // AAOs come from the L1 EthRewardsManager contract via the eth-bridge sync. +// +// Pool-managed RMs (those with a row in core_reward_pools) bypass this and +// gate on pool.authorities instead — see senderGateForRM. func (c *CoreService) gatherEligibleSenderAddresses(ctx context.Context) ([]string, error) { validators, err := c.core.db.GetAllEthAddressesOfRegisteredNodes(ctx) if err != nil { @@ -1655,27 +1791,73 @@ func (c *CoreService) gatherEligibleSenderAddresses(ctx context.Context) ([]stri return append(validators, aaos...), nil } +// senderGateForRM resolves which gating regime applies to a given Solana +// reward manager pubkey: +// +// - If a row exists in core_reward_pools for the requested RM, the pool +// is the source of truth: addAttestation iff addr ∈ pool.authorities; +// deleteAttestation iff addr ∉ pool.authorities. This is the rotation +// path — once OAP rotates a key out of the pool, validators can be +// asked to deregister it from Solana, and once a new key is rotated in, +// validators can register it. +// +// - If no pool exists for the RM, fall through to the legacy +// validator/AAO trust set (see gatherEligibleSenderAddresses). AUDIO +// deliberately stays on this path: no pool is ever created for the +// AUDIO RM (validateCreateRewardPool refuses), so AUDIO attestation +// authority remains the network-wide trust set. +// +// Returns (pool, true, nil) when pool gating applies, (nil, false, nil) +// when validator/AAO gating applies, and a non-nil error only on real DB +// failures (transient errors do NOT silently fall through to validator/AAO +// — that would let a temporary blip downgrade the gate from per-RM to +// network-wide). +func (c *CoreService) senderGateForRM(ctx context.Context, rmPubkey string) (*db.CoreRewardPool, bool, error) { + pool, err := c.core.db.GetRewardPool(ctx, rmPubkey) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, false, nil + } + return nil, false, fmt.Errorf("failed to load pool for RM %s: %w", rmPubkey, err) + } + return &pool, true, nil +} + // GetRewardSenderAttestation implements v1connect.CoreServiceHandler. +// +// Dispatches by RM: +// - If a pool exists for the requested RM, sign iff address ∈ pool.authorities. +// - Otherwise, sign iff address ∈ validator/AAO trust set (legacy AUDIO path). func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *connect.Request[v1.GetRewardSenderAttestationRequest]) (*connect.Response[v1.GetRewardSenderAttestationResponse], error) { - address := req.Msg.Address + address := strings.TrimSpace(req.Msg.Address) if address == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("address is required")) } - rewardsManagerPubkey := req.Msg.RewardsManagerPubkey + rewardsManagerPubkey := strings.TrimSpace(req.Msg.RewardsManagerPubkey) if rewardsManagerPubkey == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward manager pubkey is required")) } - eligible, err := c.gatherEligibleSenderAddresses(ctx) + pool, isPoolGated, err := c.senderGateForRM(ctx, rewardsManagerPubkey) if err != nil { return nil, connect.NewError(connect.CodeInternal, err) } - if !slices.ContainsFunc(eligible, func(eth string) bool { - return strings.EqualFold(eth, address) - }) { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("address not a registered validator or anti-abuse oracle")) + if isPoolGated { + if !contains(pool.Authorities, address) { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("address not in pool.authorities for RM %s", rewardsManagerPubkey)) + } + } else { + eligible, err := c.gatherEligibleSenderAddresses(ctx) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + if !slices.ContainsFunc(eligible, func(eth string) bool { + return strings.EqualFold(eth, address) + }) { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("address not a registered validator or anti-abuse oracle")) + } } owner, attestation, err := rewards.GetCreateSenderAttestation(c.core.config.EthereumKey, &rewards.CreateSenderAttestationParams{ @@ -1694,26 +1876,43 @@ func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *conne } // GetDeleteRewardSenderAttestation implements v1connect.CoreServiceHandler. +// +// Dispatches by RM: +// - If a pool exists for the requested RM, sign iff address ∉ pool.authorities. +// This is the rotation-out signal: OAP has already removed the key from the +// pool, and validators are catching Solana up. +// - Otherwise (AUDIO path), sign iff address is NOT in the validator/AAO +// trust set (existing behavior). func (c *CoreService) GetDeleteRewardSenderAttestation(ctx context.Context, req *connect.Request[v1.GetDeleteRewardSenderAttestationRequest]) (*connect.Response[v1.GetDeleteRewardSenderAttestationResponse], error) { - address := req.Msg.Address + address := strings.TrimSpace(req.Msg.Address) if address == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("address is required")) } - rewardsManagerPubkey := req.Msg.RewardsManagerPubkey + rewardsManagerPubkey := strings.TrimSpace(req.Msg.RewardsManagerPubkey) if rewardsManagerPubkey == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward manager pubkey is required")) } - eligible, err := c.gatherEligibleSenderAddresses(ctx) + pool, isPoolGated, err := c.senderGateForRM(ctx, rewardsManagerPubkey) if err != nil { return nil, connect.NewError(connect.CodeInternal, err) } - if slices.ContainsFunc(eligible, func(eth string) bool { - return strings.EqualFold(eth, address) - }) { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("address is a registered validator or anti-abuse oracle")) + if isPoolGated { + if contains(pool.Authorities, address) { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("address is still a current authority of the pool for RM %s; rotate it out first via SetRewardPoolAuthorities", rewardsManagerPubkey)) + } + } else { + eligible, err := c.gatherEligibleSenderAddresses(ctx) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + if slices.ContainsFunc(eligible, func(eth string) bool { + return strings.EqualFold(eth, address) + }) { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("address is a registered validator or anti-abuse oracle")) + } } owner, attestation, err := rewards.GetDeleteSenderAttestation(c.core.config.EthereumKey, &rewards.DeleteSenderAttestationParams{ diff --git a/pkg/core/server/reward_pools.go b/pkg/core/server/reward_pools.go new file mode 100644 index 00000000..248270f1 --- /dev/null +++ b/pkg/core/server/reward_pools.go @@ -0,0 +1,252 @@ +package server + +import ( + "context" + "errors" + "fmt" + "strings" + + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/OpenAudio/go-openaudio/pkg/core/config" + "github.com/OpenAudio/go-openaudio/pkg/core/db" + "github.com/OpenAudio/go-openaudio/pkg/rewards" + abcitypes "github.com/cometbft/cometbft/abci/types" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/jackc/pgx/v5" + "github.com/mr-tron/base58/base58" + "google.golang.org/protobuf/proto" +) + +// solanaPubkeyByteLen is the wire-level size of a Solana pubkey (32 bytes, +// base58-encoded). Used to validate that a rewards_manager_pubkey supplied +// to CreateRewardPool / CreateReward / SetRewardPoolAuthorities is the +// shape of an actual on-chain reward manager account, not an arbitrary +// caller-chosen string. +const solanaPubkeyByteLen = 32 + +// isValidRewardPoolTransaction is the entry point for both CheckTx and +// block validation. Signature + deadline live on the envelope; we recover +// the signer once here and pass it to per-action validators. +func (s *Server) isValidRewardPoolTransaction(ctx context.Context, signedTx *corev1.SignedTransaction, blockHeight int64) error { + envelope := signedTx.GetRewardPool() + if envelope == nil || envelope.Body == nil { + return fmt.Errorf("%w: reward pool message body is nil", ErrRewardMessageValidation) + } + + signer, err := s.recoverDeadlinedSigner(blockHeight, envelope.Body.DeadlineBlockHeight, envelope.Body, envelope.Signature) + if err != nil { + return fmt.Errorf("reward pool validation failed: %w", err) + } + + switch action := envelope.Body.Action.(type) { + case *corev1.RewardPoolBody_Create: + return s.validateCreateRewardPool(ctx, action.Create, signer) + case *corev1.RewardPoolBody_SetAuthorities: + return s.validateSetRewardPoolAuthorities(ctx, action.SetAuthorities, signer) + default: + return fmt.Errorf("%w: unsupported reward pool action type", ErrRewardMessageValidation) + } +} + +// validateCreateRewardPool: pool is identified by the Solana reward manager +// pubkey (must be valid base58 32 bytes); signer must be in the initial +// authorities; the initial authority list must be non-empty and contain +// only valid eth addresses. +func (s *Server) validateCreateRewardPool(ctx context.Context, msg *corev1.CreateRewardPool, signer string) error { + if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { + return err + } + if err := validateAuthorityList(msg.Authorities); err != nil { + return err + } + canonical := rewards.CanonicalAuthorities(msg.Authorities) + if !contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { + return fmt.Errorf("%w: signer %s not in initial authorities", ErrRewardUnauthorized, signer) + } + if _, err := s.db.GetRewardPool(ctx, msg.RewardsManagerPubkey); err == nil { + return fmt.Errorf("%w: pool %s already exists", ErrRewardMessageValidation, msg.RewardsManagerPubkey) + } else if !errors.Is(err, pgx.ErrNoRows) { + return fmt.Errorf("failed to check pool existence: %w", err) + } + return nil +} + +// validateAuthorityList enforces that an authority list is non-empty and +// every entry is a valid eth address. Without this, a current authority can +// rotate the pool to ["not-an-address"], which passes the canonicalization +// pipeline but leaves the pool with no key that can ever satisfy +// checkPoolAuthorization — every reward attached to the pool becomes +// permanently unclaimable. +func validateAuthorityList(addrs []string) error { + if len(addrs) == 0 { + return fmt.Errorf("%w: at least one authority is required", ErrRewardMessageValidation) + } + for _, a := range addrs { + if !ethcommon.IsHexAddress(strings.TrimSpace(a)) { + return fmt.Errorf("%w: %q is not a valid eth address", ErrRewardMessageValidation, a) + } + } + return nil +} + +// validateRewardsManagerPubkey checks the wire shape of a Solana reward +// manager pubkey: non-empty, base58-decodable, exactly 32 bytes. +// +// First-class pools must use a real RM pubkey because PR3's +// sender-attestation gate uses the same value to bind the pool↔RM. PR1's +// backfill resolves each existing reward row to a real RM via the +// launchpad_authority_rm mapping, so there are no synthetic-pool +// identifiers in production state to special-case here. +func validateRewardsManagerPubkey(pubkey string) error { + if pubkey == "" { + return fmt.Errorf("%w: rewards_manager_pubkey is required", ErrRewardMessageValidation) + } + if pubkey != strings.TrimSpace(pubkey) { + return fmt.Errorf("%w: rewards_manager_pubkey must not have surrounding whitespace", ErrRewardMessageValidation) + } + bytes, err := base58.Decode(pubkey) + if err != nil { + return fmt.Errorf("%w: rewards_manager_pubkey is not valid base58: %v", ErrRewardMessageValidation, err) + } + if len(bytes) != solanaPubkeyByteLen { + return fmt.Errorf("%w: rewards_manager_pubkey must decode to %d bytes; got %d", ErrRewardMessageValidation, solanaPubkeyByteLen, len(bytes)) + } + // Reserved-RM denylist: AUDIO continues to be governed by the + // network-wide validator/AAO trust set in PR3's sender-attestation + // gate (the gate falls back to validator/AAO for any RM that has no + // pool). Allowing a pool to be created for the AUDIO RM would shift + // AUDIO sender attestations to pool-controlled authorities — + // defeating the AUDIO trust model. Trim defensively: if these + // per-env constants ever start being populated from env vars or + // config, surrounding whitespace would silently disable the check. + if audioRM := strings.TrimSpace(config.AudioRewardsManagerPubkey()); audioRM != "" && pubkey == audioRM { + return fmt.Errorf("%w: rewards_manager_pubkey is reserved (AUDIO); pools cannot be created for it", ErrRewardMessageValidation) + } + return nil +} + +// validateSetRewardPoolAuthorities: rewards_manager_pubkey must be a real +// Solana RM pubkey (defense-in-depth — every pool created via +// CreateRewardPool already passed this check, but enforcing it here too +// closes any historical or future path that might have inserted a +// non-RM-shaped pool key); signer must be in the *current* pool +// authorities; the new list must be non-empty and contain only valid eth +// addresses (otherwise the pool can be rotated into a permanently-orphaned +// state). +func (s *Server) validateSetRewardPoolAuthorities(ctx context.Context, msg *corev1.SetRewardPoolAuthorities, signer string) error { + if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { + return err + } + if err := validateAuthorityList(msg.Authorities); err != nil { + return err + } + return s.checkPoolAuthorization(ctx, s.db, msg.RewardsManagerPubkey, signer) +} + +// finalizeRewardPoolTransaction is invoked after a tx is included in a block. +// Signature was already verified at validate time; we re-recover here only +// because the finalize path is its own consensus boundary. +func (s *Server) finalizeRewardPoolTransaction(ctx context.Context, req *abcitypes.FinalizeBlockRequest, envelope *corev1.RewardPoolMessage, txhash string, messageIndex int64) (proto.Message, error) { + if envelope == nil || envelope.Body == nil { + return nil, fmt.Errorf("tx: %s, message index: %d, reward pool message body not found", txhash, messageIndex) + } + signer, err := s.recoverDeadlinedSigner(req.Height, envelope.Body.DeadlineBlockHeight, envelope.Body, envelope.Signature) + if err != nil { + return nil, errors.Join(ErrRewardMessageFinalization, fmt.Errorf("signer recovery: %w", err)) + } + + switch action := envelope.Body.Action.(type) { + case *corev1.RewardPoolBody_Create: + if err := s.finalizeCreateRewardPool(ctx, action.Create, signer); err != nil { + return nil, errors.Join(ErrRewardMessageFinalization, err) + } + case *corev1.RewardPoolBody_SetAuthorities: + if err := s.finalizeSetRewardPoolAuthorities(ctx, action.SetAuthorities, signer); err != nil { + return nil, errors.Join(ErrRewardMessageFinalization, err) + } + default: + return nil, fmt.Errorf("tx: %s, message index: %d, unsupported reward pool action type", txhash, messageIndex) + } + return envelope, nil +} + +// finalizeCreateRewardPool: pool address == rewards_manager_pubkey. Same-RM +// in-block collisions surface as a PK violation from InsertRewardPool, which +// fails the tx (block continues; no chain crash). +// +// Re-validates the message shape at finalize time as defense-in-depth: +// block-sync replay calls FinalizeBlock without re-running ProcessProposal, +// so any malformed bytes that ever reached the chain would skip the +// validate-time checks. Repeating the same shape + signer-membership +// validation here keeps the post-replay state identical to what the live +// validate path produces. +func (s *Server) finalizeCreateRewardPool(ctx context.Context, msg *corev1.CreateRewardPool, signer string) error { + if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { + return err + } + if err := validateAuthorityList(msg.Authorities); err != nil { + return err + } + canonical := rewards.CanonicalAuthorities(msg.Authorities) + if !contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { + return fmt.Errorf("%w: signer %s not in initial authorities", ErrRewardUnauthorized, signer) + } + return s.getDb().InsertRewardPool(ctx, db.InsertRewardPoolParams{ + RewardsManagerPubkey: msg.RewardsManagerPubkey, + Authorities: canonical, + }) +} + +// finalizeSetRewardPoolAuthorities re-checks signer authorization against +// post-prior-tx state via s.getDb(), guarding against same-block ordering +// where an earlier tx rotates the signer out before this one runs. +// +// Also re-runs the same shape validation as the live validate path +// (defense-in-depth, matching finalizeCreateRewardPool): block-sync +// replay calls FinalizeBlock without re-running ProcessProposal, so any +// malformed bytes that ever reached the chain would skip the validate- +// time checks. Repeating them here keeps the post-replay state identical +// to live and prevents an orphaned/empty-authority pool from being +// written. +func (s *Server) finalizeSetRewardPoolAuthorities(ctx context.Context, msg *corev1.SetRewardPoolAuthorities, signer string) error { + if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { + return err + } + if err := validateAuthorityList(msg.Authorities); err != nil { + return err + } + if err := s.checkPoolAuthorization(ctx, s.getDb(), msg.RewardsManagerPubkey, signer); err != nil { + return err + } + return s.getDb().UpdateRewardPoolAuthorities(ctx, db.UpdateRewardPoolAuthoritiesParams{ + RewardsManagerPubkey: msg.RewardsManagerPubkey, + Authorities: rewards.CanonicalAuthorities(msg.Authorities), + }) +} + +// checkPoolAuthorization fetches the pool from the supplied queries handle +// (s.db at validate time, s.getDb() at finalize time) and verifies signer is +// in the current authority set. Used by both validate and finalize paths so +// the rule lives in one place. +func (s *Server) checkPoolAuthorization(ctx context.Context, q *db.Queries, rewardsManagerPubkey, signer string) error { + pool, err := q.GetRewardPool(ctx, rewardsManagerPubkey) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return fmt.Errorf("%w: pool %s not found", ErrRewardMessageValidation, rewardsManagerPubkey) + } + return fmt.Errorf("failed to load pool %s: %w", rewardsManagerPubkey, err) + } + if !contains(pool.Authorities, strings.ToLower(strings.TrimSpace(signer))) { + return fmt.Errorf("%w: signer %s not authorized for pool %s", ErrRewardUnauthorized, signer, rewardsManagerPubkey) + } + return nil +} + +func contains(haystack []string, needle string) bool { + for _, h := range haystack { + if strings.EqualFold(h, needle) { + return true + } + } + return false +} diff --git a/pkg/core/server/reward_pools_test.go b/pkg/core/server/reward_pools_test.go new file mode 100644 index 00000000..7336d202 --- /dev/null +++ b/pkg/core/server/reward_pools_test.go @@ -0,0 +1,95 @@ +package server + +import ( + "crypto/rand" + "strings" + "testing" + + "github.com/OpenAudio/go-openaudio/pkg/core/config" + "github.com/mr-tron/base58/base58" +) + +// TestValidateRewardsManagerPubkey_BaseChecks covers shape rejection: +// empty, surrounding whitespace, non-base58, and wrong byte length. +// The AUDIO denylist is exercised separately by +// TestValidateRewardsManagerPubkey_AudioDenylist. +func TestValidateRewardsManagerPubkey_BaseChecks(t *testing.T) { + good := freshSolanaPubkeyForTest(t) + + cases := []struct { + name string + pubkey string + wantErr string + }{ + {"empty", "", "is required"}, + {"leading whitespace", " " + good, "whitespace"}, + {"trailing whitespace", good + " ", "whitespace"}, + {"non-base58", "this!is@not%base58", "not valid base58"}, + {"too few bytes", base58.Encode([]byte("short")), "must decode to 32 bytes"}, + {"good", good, ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := validateRewardsManagerPubkey(tc.pubkey) + if tc.wantErr == "" { + if err != nil { + t.Fatalf("expected pass; got %v", err) + } + return + } + if err == nil { + t.Fatalf("expected error containing %q; got nil", tc.wantErr) + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("error %q does not contain %q", err.Error(), tc.wantErr) + } + }) + } +} + +// TestValidateRewardsManagerPubkey_AudioDenylist verifies that a CreateRewardPool +// targeting the configured AUDIO RM is refused. We swap in a test-only +// AUDIO RM via the per-env var (this test runs in the default 'prod' +// runtime environment per GetRuntimeEnvironment's fallback), so we set +// ProdAudioRewardsManagerPubkey for the duration of the test. +func TestValidateRewardsManagerPubkey_AudioDenylist(t *testing.T) { + // Two distinct, well-formed pubkeys: one will be the configured AUDIO RM + // (denylisted), the other an arbitrary launchpad RM (allowed). + audioRM := freshSolanaPubkeyForTest(t) + otherRM := freshSolanaPubkeyForTest(t) + + // Save and restore so the test is hermetic. + prevDev := config.DevAudioRewardsManagerPubkey + prevStage := config.StageAudioRewardsManagerPubkey + prevProd := config.ProdAudioRewardsManagerPubkey + defer func() { + config.DevAudioRewardsManagerPubkey = prevDev + config.StageAudioRewardsManagerPubkey = prevStage + config.ProdAudioRewardsManagerPubkey = prevProd + }() + // Set all three so the test passes regardless of which env happens to be + // resolved (default is "prod" per GetRuntimeEnvironment). + config.DevAudioRewardsManagerPubkey = audioRM + config.StageAudioRewardsManagerPubkey = audioRM + config.ProdAudioRewardsManagerPubkey = audioRM + + if err := validateRewardsManagerPubkey(audioRM); err == nil { + t.Fatalf("expected AUDIO RM to be rejected by denylist") + } else if !strings.Contains(err.Error(), "reserved") { + t.Fatalf("expected 'reserved' error; got %v", err) + } + + if err := validateRewardsManagerPubkey(otherRM); err != nil { + t.Fatalf("expected non-AUDIO RM to pass; got %v", err) + } +} + +func freshSolanaPubkeyForTest(t *testing.T) string { + t.Helper() + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + t.Fatalf("rand: %v", err) + } + return base58.Encode(b[:]) +} diff --git a/pkg/core/server/rewards.go b/pkg/core/server/rewards.go index 0062281c..46c4a3ef 100644 --- a/pkg/core/server/rewards.go +++ b/pkg/core/server/rewards.go @@ -2,18 +2,18 @@ package server import ( "context" - "encoding/hex" "errors" "fmt" "net/http" "strconv" - "strings" corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" "github.com/OpenAudio/go-openaudio/pkg/core/db" "github.com/OpenAudio/go-openaudio/pkg/rewards" abcitypes "github.com/cometbft/cometbft/abci/types" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" "google.golang.org/protobuf/proto" ) @@ -91,75 +91,94 @@ var ( ) func (s *Server) isValidRewardTransaction(ctx context.Context, signedTx *corev1.SignedTransaction, blockHeight int64) error { - rewardMsg := signedTx.GetReward() - if rewardMsg == nil { + envelope := signedTx.GetReward() + if envelope == nil { return fmt.Errorf("%w: reward message is nil", ErrRewardMessageValidation) } + if envelope.Body == nil { + // Legacy wire-format detected (pre-pool-rollout shape with deadline + + // signature embedded in CreateReward / DeleteReward at tags 1000/1001). + // + // We REJECT legacy here — at CheckTx and ProcessProposal — because + // legacy CreateReward is inherently permissionless: it carries no + // pool_address and the old signing scheme had no membership check on + // claim_authorities. Allowing live legacy txs through validation would + // reopen the exact exploit class this PR is closing (an attacker + // crafts legacy bytes, picks any reward_id / amount / claim_authorities, + // and bypasses the pool gate). + // + // The corresponding code path in finalizeRewards still APPLIES legacy + // txs — that's intentional, for block-sync-from-genesis replay of + // already-committed historical blocks. Block sync only invokes + // FinalizeBlock; it does not re-run CheckTx or ProcessProposal. So + // "reject at validate, accept at finalize" gives us correct historical + // replay without admitting any new legacy traffic. + if legacy, err := tryParseLegacyReward(envelope); err == nil && legacy != nil { + return fmt.Errorf("%w: legacy reward wire format is not accepted for new transactions; clients must use the body+signature envelope", ErrRewardMessageValidation) + } + return fmt.Errorf("%w: reward message body is nil", ErrRewardMessageValidation) + } + + signer, err := s.recoverDeadlinedSigner(blockHeight, envelope.Body.DeadlineBlockHeight, envelope.Body, envelope.Signature) + if err != nil { + return fmt.Errorf("reward validation failed: %w", err) + } - switch action := rewardMsg.Action.(type) { - case *corev1.RewardMessage_Create: - return s.validateCreateReward(ctx, action.Create, blockHeight) - case *corev1.RewardMessage_Delete: - return s.validateDeleteReward(ctx, action.Delete, blockHeight) + switch action := envelope.Body.Action.(type) { + case *corev1.RewardBody_Create: + return s.validateCreateReward(ctx, action.Create, signer) + case *corev1.RewardBody_Delete: + return s.validateDeleteReward(ctx, action.Delete, signer) default: return fmt.Errorf("%w: unsupported reward action type", ErrRewardMessageValidation) } } -func (s *Server) validateCreateReward(_ context.Context, createReward *corev1.CreateReward, blockHeight int64) error { - signatureData := common.CreateDeterministicCreateRewardData(createReward) - _, err := s.validateRewardSignature(blockHeight, createReward.Signature, createReward.DeadlineBlockHeight, signatureData) - if err != nil { - return fmt.Errorf("create reward validation failed: %w", err) - } - return nil +func (s *Server) validateCreateReward(ctx context.Context, createReward *corev1.CreateReward, signer string) error { + // New CreateReward requires a first-class pool. The pool is identified + // by the Solana reward manager pubkey, and only pool members can issue + // rewards under that RM. Without this binding, a member of one pool + // could issue rewards under any other pool's RM and inherit its + // attestation rights — which is exactly what PR3's per-RM gate is + // designed to prevent. + // + // The legacy permissionless path (inline claim_authorities, no RM) is + // preserved only for historical replay via the wire-compat layer; new + // live txs must specify rewards_manager_pubkey. + if err := validateRewardsManagerPubkey(createReward.RewardsManagerPubkey); err != nil { + return err + } + return s.checkPoolAuthorization(ctx, s.db, createReward.RewardsManagerPubkey, signer) } -func (s *Server) validateDeleteReward(ctx context.Context, deleteReward *corev1.DeleteReward, blockHeight int64) error { - signatureData := common.CreateDeterministicDeleteRewardData(deleteReward) - signer, err := s.validateRewardSignature(blockHeight, deleteReward.Signature, deleteReward.DeadlineBlockHeight, signatureData) - if err != nil { - return fmt.Errorf("delete reward validation failed: %w", err) - } - +func (s *Server) validateDeleteReward(ctx context.Context, deleteReward *corev1.DeleteReward, signer string) error { existingReward, err := s.db.GetReward(ctx, deleteReward.Address) if err != nil { - return fmt.Errorf("failed to get existing reward for validation: %w", err) - } - - authorized := false - for _, auth := range existingReward.ClaimAuthorities { - if strings.EqualFold(auth, signer) { - authorized = true - break + if errors.Is(err, pgx.ErrNoRows) { + return fmt.Errorf("%w: reward %s not found", ErrRewardMessageValidation, deleteReward.Address) } + return fmt.Errorf("failed to get existing reward for validation: %w", err) } - if !authorized { + if !contains(existingReward.ClaimAuthorities, signer) { return fmt.Errorf("%w: signer %s not authorized to delete reward %s", ErrRewardUnauthorized, signer, deleteReward.Address) } - return nil } -// validateRewardSignature validates the signature and expiry for reward messages -func (s *Server) validateRewardSignature(currentHeight int64, signature string, deadlineHeight int64, signatureData string) (string, error) { - // Check expiry +// recoverDeadlinedSigner enforces the message's deadline against the current +// block height and recovers the eth address that produced signature over +// the deterministic-protobuf marshaling of msg. msg is expected to be a +// signed body (RewardBody / RewardPoolBody) — there is no signature field +// on the body, so common.ProtoRecover marshals it as-is. Used by every +// reward / reward-pool validator and finalizer. +func (s *Server) recoverDeadlinedSigner(currentHeight, deadlineHeight int64, msg proto.Message, signature string) (string, error) { if currentHeight > deadlineHeight { return "", fmt.Errorf("%w: current height %d > deadline %d", ErrRewardExpired, currentHeight, deadlineHeight) } - - // Convert hex data to bytes for signing - dataBytes, err := hex.DecodeString(signatureData) + signer, err := common.ProtoRecover(msg, signature) if err != nil { - return "", fmt.Errorf("%w: invalid hex data: %v", ErrRewardSignatureInvalid, err) + return "", fmt.Errorf("%w: %v", ErrRewardSignatureInvalid, err) } - - // Recover signer from signature - _, signer, err := common.EthRecover(signature, dataBytes) - if err != nil { - return "", fmt.Errorf("%w: failed to recover signer: %v", ErrRewardSignatureInvalid, err) - } - return signer, nil } @@ -172,20 +191,36 @@ func (s *Server) finalizeRewardTransaction(ctx context.Context, req *abcitypes.F return rewardMsg, nil } -func (s *Server) finalizeRewards(ctx context.Context, req *abcitypes.FinalizeBlockRequest, txhash string, messageIndex int64, rewardMsg *corev1.RewardMessage, sender string) error { - if rewardMsg == nil { +func (s *Server) finalizeRewards(ctx context.Context, req *abcitypes.FinalizeBlockRequest, txhash string, messageIndex int64, envelope *corev1.RewardMessage, sender string) error { + if envelope == nil { return fmt.Errorf("tx: %s, message index: %d, reward message not found", txhash, messageIndex) } + if envelope.Body == nil { + // Legacy wire-format path; see rewards_legacy.go. + legacy, err := tryParseLegacyReward(envelope) + if err != nil { + return errors.Join(ErrRewardMessageFinalization, err) + } + if legacy == nil { + return fmt.Errorf("tx: %s, message index: %d, reward message body not found", txhash, messageIndex) + } + return s.finalizeLegacyRewardTransaction(ctx, req, legacy, txhash, messageIndex) + } - switch action := rewardMsg.Action.(type) { - case *corev1.RewardMessage_Create: - if err := s.finalizeCreateReward(ctx, req, txhash, messageIndex, action.Create, sender); err != nil { + signer, err := s.recoverDeadlinedSigner(req.Height, envelope.Body.DeadlineBlockHeight, envelope.Body, envelope.Signature) + if err != nil { + return errors.Join(ErrRewardMessageFinalization, fmt.Errorf("signature validation failed: %w", err)) + } + + switch action := envelope.Body.Action.(type) { + case *corev1.RewardBody_Create: + if err := s.finalizeCreateReward(ctx, req, txhash, messageIndex, action.Create, signer); err != nil { return errors.Join(ErrRewardMessageFinalization, err) } return nil - case *corev1.RewardMessage_Delete: - if err := s.finalizeDeleteReward(ctx, req, txhash, messageIndex, action.Delete, sender); err != nil { + case *corev1.RewardBody_Delete: + if err := s.finalizeDeleteReward(ctx, action.Delete, signer); err != nil { return errors.Join(ErrRewardMessageFinalization, err) } return nil @@ -195,45 +230,37 @@ func (s *Server) finalizeRewards(ctx context.Context, req *abcitypes.FinalizeBlo } } -func (s *Server) finalizeCreateReward(ctx context.Context, req *abcitypes.FinalizeBlockRequest, txhash string, messageIndex int64, createReward *corev1.CreateReward, sender string) error { - // Validate signature and get signer - signatureData := common.CreateDeterministicCreateRewardData(createReward) - signer, err := s.validateRewardSignature(req.Height, createReward.Signature, createReward.DeadlineBlockHeight, signatureData) - if err != nil { - return fmt.Errorf("create reward signature validation failed: %w", err) +func (s *Server) finalizeCreateReward(ctx context.Context, req *abcitypes.FinalizeBlockRequest, txhash string, messageIndex int64, createReward *corev1.CreateReward, signer string) error { + // New CreateReward must reference an existing first-class pool. Re-check + // authorization against post-prior-tx state — block ordering can rotate + // the signer out between validate and finalize. + qtx := s.getDb() + if err := s.checkPoolAuthorization(ctx, qtx, createReward.RewardsManagerPubkey, signer); err != nil { + return err } - // Generate deterministic address for the new reward txhashBytes, err := common.HexToBytes(txhash) if err != nil { return fmt.Errorf("invalid txhash: %w", err) } rewardAddress := common.CreateAddress(txhashBytes, s.config.GenesisFile.ChainID, req.Height, messageIndex, "") - // Convert claim authorities to string array - claimAuthorities := make([]string, len(createReward.ClaimAuthorities)) - for i, auth := range createReward.ClaimAuthorities { - claimAuthorities[i] = auth.Address - } - - // Marshal the raw message rawMessage, err := proto.Marshal(createReward) if err != nil { return fmt.Errorf("failed to marshal create reward message: %w", err) } - qtx := s.getDb() if err := qtx.InsertCoreReward(ctx, db.InsertCoreRewardParams{ - TxHash: txhash, - Index: messageIndex, - Address: rewardAddress, - Sender: signer, // Use verified signer instead of passed sender - RewardID: createReward.RewardId, - Name: createReward.Name, - Amount: int64(createReward.Amount), - ClaimAuthorities: claimAuthorities, - RawMessage: rawMessage, - BlockHeight: req.Height, + TxHash: txhash, + Index: messageIndex, + Address: rewardAddress, + Sender: signer, + RewardID: createReward.RewardId, + Name: createReward.Name, + Amount: int64(createReward.Amount), + RewardsManagerPubkey: pgtype.Text{String: createReward.RewardsManagerPubkey, Valid: true}, + RawMessage: rawMessage, + BlockHeight: req.Height, }); err != nil { return fmt.Errorf("failed to insert reward: %w", err) } @@ -241,36 +268,23 @@ func (s *Server) finalizeCreateReward(ctx context.Context, req *abcitypes.Finali return nil } -func (s *Server) finalizeDeleteReward(ctx context.Context, req *abcitypes.FinalizeBlockRequest, txhash string, messageIndex int64, deleteReward *corev1.DeleteReward, sender string) error { - // Validate signature and get signer - signatureData := common.CreateDeterministicDeleteRewardData(deleteReward) - signer, err := s.validateRewardSignature(req.Height, deleteReward.Signature, deleteReward.DeadlineBlockHeight, signatureData) - if err != nil { - return fmt.Errorf("delete reward signature validation failed: %w", err) - } - - // Verify signer is authorized to delete this reward - existingReward, err := s.getDb().GetReward(ctx, deleteReward.Address) +func (s *Server) finalizeDeleteReward(ctx context.Context, deleteReward *corev1.DeleteReward, signer string) error { + // Re-check authorization against post-prior-tx state — a sibling tx in + // the same block can rotate the signer out of the reward's pool between + // validate (pre-block state) and finalize (post-prior-tx state). + qtx := s.getDb() + existingReward, err := qtx.GetReward(ctx, deleteReward.Address) if err != nil { - return fmt.Errorf("failed to get existing reward: %w", err) - } - - // Check if signer is in the claim authorities (case insensitive) - authorized := false - for _, auth := range existingReward.ClaimAuthorities { - if strings.EqualFold(auth, signer) { - authorized = true - break + if errors.Is(err, pgx.ErrNoRows) { + return fmt.Errorf("%w: reward %s not found", ErrRewardMessageValidation, deleteReward.Address) } + return fmt.Errorf("failed to get reward at finalize: %w", err) } - if !authorized { - return fmt.Errorf("signer %s not authorized to delete reward %s", signer, deleteReward.Address) + if !contains(existingReward.ClaimAuthorities, signer) { + return fmt.Errorf("%w: signer %s no longer authorized to delete reward %s", ErrRewardUnauthorized, signer, deleteReward.Address) } - - qtx := s.getDb() if err := qtx.DeleteCoreReward(ctx, deleteReward.Address); err != nil { return fmt.Errorf("failed to delete reward: %w", err) } - return nil } diff --git a/pkg/core/server/rewards_legacy.go b/pkg/core/server/rewards_legacy.go new file mode 100644 index 00000000..af5222a6 --- /dev/null +++ b/pkg/core/server/rewards_legacy.go @@ -0,0 +1,202 @@ +package server + +import ( + "context" + "errors" + "fmt" + + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/OpenAudio/go-openaudio/pkg/common" + "github.com/OpenAudio/go-openaudio/pkg/core/db" + "github.com/OpenAudio/go-openaudio/pkg/rewards" + abcitypes "github.com/cometbft/cometbft/abci/types" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "google.golang.org/protobuf/proto" +) + +// Legacy reward wire-compat layer. +// +// Pre-pool-rollout, RewardMessage was a oneof of CreateReward / DeleteReward +// with deadline + signature embedded inside each action. PR #225 introduced +// the body+signature envelope, which is wire-incompatible: legacy bytes have +// no field at tag 1 (the new body) so the new proto unmarshals them with +// Body == nil. +// +// This file handles the case where a legacy-shaped reward tx is encountered +// during block-sync-from-genesis. We re-decode the unknown fields as +// LegacyRewardMessage, recover the signer using the legacy signing scheme +// (sha256 over a pipe-delimited canonical string — see +// pkg/common/legacy_reward_signing.go), and apply the same business logic +// the new code applies to a CreateReward / DeleteReward without pool_address +// (synthetic-pool fallback for create; claim-authorities check for delete). +// +// Live-traffic note: post-rollout, no client should produce legacy bytes — +// the SDK and API repo only emit the new envelope. This path is dormant +// after the migration window and only activates for replay. + +// tryParseLegacyReward inspects a RewardMessage that decoded with Body == nil +// and re-attempts to decode its bytes as the legacy oneof shape. Returns nil +// if no legacy action is present (e.g., the bytes really were empty / from a +// future incompatible version). +func tryParseLegacyReward(rm *corev1.RewardMessage) (*corev1.LegacyRewardMessage, error) { + if rm == nil || rm.Body != nil { + return nil, nil + } + // proto-go preserves unknown fields by default; round-tripping the new + // RewardMessage through Marshal recovers the original tag 1000/1001 bytes. + raw, err := proto.Marshal(rm) + if err != nil { + return nil, fmt.Errorf("legacy reward: re-marshal: %w", err) + } + if len(raw) == 0 { + return nil, nil + } + var legacy corev1.LegacyRewardMessage + if err := proto.Unmarshal(raw, &legacy); err != nil { + return nil, fmt.Errorf("legacy reward: unmarshal: %w", err) + } + if legacy.Action == nil { + return nil, nil + } + return &legacy, nil +} + +// finalizeLegacyRewardTransaction applies a legacy-shape reward tx. For +// CreateReward, the reward's RM is resolved by looking up any one of its +// inline claim_authorities in the launchpad_authority_rm mapping (PR1's +// migration populates this table); rewards whose authorities don't match +// any known launchpad authority are inserted with NULL rewards_manager_pubkey. +// For DeleteReward, the row is removed after re-checking authorization +// against the existing reward's pool authorities. +func (s *Server) finalizeLegacyRewardTransaction(ctx context.Context, req *abcitypes.FinalizeBlockRequest, legacy *corev1.LegacyRewardMessage, txhash string, messageIndex int64) error { + switch a := legacy.Action.(type) { + case *corev1.LegacyRewardMessage_Create: + signer, err := s.recoverLegacyCreateRewardSigner(req.Height, a.Create) + if err != nil { + return errors.Join(ErrRewardMessageFinalization, err) + } + if err := s.finalizeLegacyCreateReward(ctx, req, txhash, messageIndex, a.Create, signer); err != nil { + return errors.Join(ErrRewardMessageFinalization, err) + } + return nil + case *corev1.LegacyRewardMessage_Delete: + signer, err := s.recoverLegacyDeleteRewardSigner(req.Height, a.Delete) + if err != nil { + return errors.Join(ErrRewardMessageFinalization, err) + } + if err := s.finalizeLegacyDeleteReward(ctx, a.Delete, signer); err != nil { + return errors.Join(ErrRewardMessageFinalization, err) + } + return nil + default: + return fmt.Errorf("tx: %s, message index: %d, unknown legacy reward action", txhash, messageIndex) + } +} + +func (s *Server) recoverLegacyCreateRewardSigner(currentHeight int64, cr *corev1.LegacyCreateReward) (string, error) { + if currentHeight > cr.DeadlineBlockHeight { + return "", fmt.Errorf("%w: current height %d > deadline %d", ErrRewardExpired, currentHeight, cr.DeadlineBlockHeight) + } + signer, err := common.LegacyRecoverCreateReward(cr) + if err != nil { + return "", fmt.Errorf("%w: %v", ErrRewardSignatureInvalid, err) + } + return signer, nil +} + +func (s *Server) recoverLegacyDeleteRewardSigner(currentHeight int64, dr *corev1.LegacyDeleteReward) (string, error) { + if currentHeight > dr.DeadlineBlockHeight { + return "", fmt.Errorf("%w: current height %d > deadline %d", ErrRewardExpired, currentHeight, dr.DeadlineBlockHeight) + } + signer, err := common.LegacyRecoverDeleteReward(dr) + if err != nil { + return "", fmt.Errorf("%w: %v", ErrRewardSignatureInvalid, err) + } + return signer, nil +} + +func (s *Server) finalizeLegacyCreateReward(ctx context.Context, req *abcitypes.FinalizeBlockRequest, txhash string, messageIndex int64, cr *corev1.LegacyCreateReward, signer string) error { + txhashBytes, err := common.HexToBytes(txhash) + if err != nil { + return fmt.Errorf("invalid txhash: %w", err) + } + rewardAddress := common.CreateAddress(txhashBytes, s.config.GenesisFile.ChainID, req.Height, messageIndex, "") + + qtx := s.getDb() + + // Resolve the reward's RM by looking for a launchpad-derived authority + // among the message's inline claim_authorities. The launchpad_authority_rm + // table (populated in PR1) maps each per-mint claim authority (lowercased + // eth hex) to the Solana reward manager state account that mint's rewards + // live under. This produces the SAME (RM, authorities) state PR1's + // backfill produced for pre-migration rows, so historical replay and the + // migration agree on the resulting apphash. + authorityAddrs := make([]string, 0, len(cr.ClaimAuthorities)) + for _, auth := range cr.ClaimAuthorities { + authorityAddrs = append(authorityAddrs, auth.Address) + } + canonicalAuthorities := rewards.CanonicalAuthorities(authorityAddrs) + + var rmPubkey pgtype.Text + if len(canonicalAuthorities) > 0 { + rm, err := qtx.GetLaunchpadRMByAuthority(ctx, canonicalAuthorities) + switch { + case errors.Is(err, pgx.ErrNoRows): + // No matching launchpad authority. Reward stays orphaned (NULL + // rewards_manager_pubkey, no pool). Mirrors what the migration + // does for rows whose claim_authorities don't include any known + // launchpad-derived authority. + case err != nil: + return fmt.Errorf("failed to resolve launchpad RM: %w", err) + default: + // Upsert the pool so any subsequent legacy replays of rewards + // targeting the same RM converge on the same authority set. + if err := qtx.UpsertSyntheticRewardPool(ctx, db.UpsertSyntheticRewardPoolParams{ + RewardsManagerPubkey: rm, + Authorities: canonicalAuthorities, + }); err != nil { + return fmt.Errorf("failed to upsert reward pool for legacy replay: %w", err) + } + rmPubkey = pgtype.Text{String: rm, Valid: true} + } + } + + rawMessage, err := proto.Marshal(cr) + if err != nil { + return fmt.Errorf("failed to marshal legacy create reward: %w", err) + } + if err := qtx.InsertCoreReward(ctx, db.InsertCoreRewardParams{ + TxHash: txhash, + Index: messageIndex, + Address: rewardAddress, + Sender: signer, + RewardID: cr.RewardId, + Name: cr.Name, + Amount: int64(cr.Amount), + RewardsManagerPubkey: rmPubkey, + RawMessage: rawMessage, + BlockHeight: req.Height, + }); err != nil { + return fmt.Errorf("failed to insert legacy reward: %w", err) + } + return nil +} + +func (s *Server) finalizeLegacyDeleteReward(ctx context.Context, dr *corev1.LegacyDeleteReward, signer string) error { + qtx := s.getDb() + existingReward, err := qtx.GetReward(ctx, dr.Address) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return fmt.Errorf("%w: reward %s not found", ErrRewardMessageValidation, dr.Address) + } + return fmt.Errorf("legacy delete: failed to get reward: %w", err) + } + if !contains(existingReward.ClaimAuthorities, signer) { + return fmt.Errorf("%w: legacy signer %s no longer authorized to delete reward %s", ErrRewardUnauthorized, signer, dr.Address) + } + if err := qtx.DeleteCoreReward(ctx, dr.Address); err != nil { + return fmt.Errorf("failed to delete legacy reward: %w", err) + } + return nil +} diff --git a/pkg/core/server/rewards_legacy_test.go b/pkg/core/server/rewards_legacy_test.go new file mode 100644 index 00000000..c08fbb6c --- /dev/null +++ b/pkg/core/server/rewards_legacy_test.go @@ -0,0 +1,226 @@ +package server + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "strings" + "testing" + + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/OpenAudio/go-openaudio/pkg/common" + "github.com/ethereum/go-ethereum/crypto" + "google.golang.org/protobuf/proto" +) + +// TestTryParseLegacyReward_ProtoUnknownFieldRoundtrip exercises the heart of +// the wire-compat layer: when a legacy CreateReward (encoded against the old +// RewardMessage shape) is unmarshaled by the new RewardMessage proto, the +// new proto's Body is nil, but the original tag-1000 bytes survive as +// preserved unknown fields. tryParseLegacyReward re-marshals and re-decodes +// those unknown bytes as LegacyRewardMessage and recovers the action. +func TestTryParseLegacyReward_ProtoUnknownFieldRoundtrip(t *testing.T) { + priv, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("genkey: %v", err) + } + signer := crypto.PubkeyToAddress(priv.PublicKey).Hex() + + t.Run("LegacyCreateReward", func(t *testing.T) { + legacyCreate := &corev1.LegacyCreateReward{ + RewardId: "legacy-r-1", + Name: "Legacy 1", + Amount: 42, + ClaimAuthorities: []*corev1.ClaimAuthority{{Address: signer, Name: "Owner"}}, + DeadlineBlockHeight: 999_999, + } + // Sign with the legacy scheme and stash the signature back on the proto. + sig := signLegacyCreate(t, priv, legacyCreate) + legacyCreate.Signature = sig + + legacyMsg := &corev1.LegacyRewardMessage{ + Action: &corev1.LegacyRewardMessage_Create{Create: legacyCreate}, + } + // Marshal as legacy bytes — what an old node would have written to chain. + legacyBytes, err := proto.Marshal(legacyMsg) + if err != nil { + t.Fatalf("marshal legacy: %v", err) + } + + // Unmarshal those bytes into the *new* RewardMessage shape. Tag 1000 is + // unknown to the new shape; proto-go preserves it as unknown fields. + var newMsg corev1.RewardMessage + if err := proto.Unmarshal(legacyBytes, &newMsg); err != nil { + t.Fatalf("unmarshal new shape: %v", err) + } + if newMsg.Body != nil { + t.Fatalf("expected new shape to decode with Body == nil") + } + + // Wire-compat layer should recover the legacy action. + recovered, err := tryParseLegacyReward(&newMsg) + if err != nil { + t.Fatalf("tryParseLegacyReward: %v", err) + } + if recovered == nil { + t.Fatalf("expected legacy action to be recovered") + } + create := recovered.GetCreate() + if create == nil { + t.Fatalf("expected create action; got %T", recovered.Action) + } + if create.RewardId != "legacy-r-1" || create.Amount != 42 { + t.Fatalf("recovered fields wrong: %+v", create) + } + + // Signer recovery against the legacy signing scheme must yield the + // original key. + recoveredSigner, err := common.LegacyRecoverCreateReward(create) + if err != nil { + t.Fatalf("recover signer: %v", err) + } + if !strings.EqualFold(recoveredSigner, signer) { + t.Fatalf("recovered signer %q want %q", recoveredSigner, signer) + } + }) + + t.Run("LegacyDeleteReward", func(t *testing.T) { + legacyDelete := &corev1.LegacyDeleteReward{ + Address: "0xreward", + DeadlineBlockHeight: 100, + } + sig := signLegacyDelete(t, priv, legacyDelete) + legacyDelete.Signature = sig + + legacyMsg := &corev1.LegacyRewardMessage{ + Action: &corev1.LegacyRewardMessage_Delete{Delete: legacyDelete}, + } + legacyBytes, err := proto.Marshal(legacyMsg) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var newMsg corev1.RewardMessage + if err := proto.Unmarshal(legacyBytes, &newMsg); err != nil { + t.Fatalf("unmarshal: %v", err) + } + recovered, err := tryParseLegacyReward(&newMsg) + if err != nil { + t.Fatalf("tryParseLegacyReward: %v", err) + } + if recovered == nil || recovered.GetDelete() == nil { + t.Fatalf("expected delete action recovered") + } + got, err := common.LegacyRecoverDeleteReward(recovered.GetDelete()) + if err != nil { + t.Fatalf("recover: %v", err) + } + if !strings.EqualFold(got, signer) { + t.Fatalf("recovered %q want %q", got, signer) + } + }) + + t.Run("NewShapeIsLeftAlone", func(t *testing.T) { + // A genuine new-shape RewardMessage (Body populated) must NOT trip the + // legacy path; tryParseLegacyReward returns (nil, nil) and the + // caller's existing dispatch handles it. + newMsg := &corev1.RewardMessage{ + Body: &corev1.RewardBody{ + DeadlineBlockHeight: 1, + Action: &corev1.RewardBody_Delete{Delete: &corev1.DeleteReward{Address: "0xnew"}}, + }, + Signature: "deadbeef", + } + recovered, err := tryParseLegacyReward(newMsg) + if err != nil { + t.Fatalf("err on new shape: %v", err) + } + if recovered != nil { + t.Fatalf("legacy path triggered on new-shape message") + } + }) + + t.Run("EmptyMessageIsNotLegacy", func(t *testing.T) { + var empty corev1.RewardMessage + recovered, err := tryParseLegacyReward(&empty) + if err != nil { + t.Fatalf("err: %v", err) + } + if recovered != nil { + t.Fatalf("expected nil for empty message; got %+v", recovered) + } + }) +} + +// TestIsValidRewardTransaction_RejectsLegacyAtValidate confirms the +// asymmetric gate: live-validation paths (CheckTx, ProcessProposal) refuse +// legacy-format reward txs even though FinalizeBlock still applies them +// during historical replay. This closes the loophole where an attacker +// could craft legacy bytes (with arbitrary inline claim_authorities) and +// bypass the new pool gate, since legacy CreateReward had no +// signer-membership check. +func TestIsValidRewardTransaction_RejectsLegacyAtValidate(t *testing.T) { + priv, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("genkey: %v", err) + } + cr := &corev1.LegacyCreateReward{ + RewardId: "live-legacy", + Name: "should be rejected", + Amount: 1, + ClaimAuthorities: []*corev1.ClaimAuthority{{Address: crypto.PubkeyToAddress(priv.PublicKey).Hex(), Name: "x"}}, + DeadlineBlockHeight: 999_999, + } + cr.Signature = signLegacyCreate(t, priv, cr) + legacyMsg := &corev1.LegacyRewardMessage{Action: &corev1.LegacyRewardMessage_Create{Create: cr}} + legacyBytes, err := proto.Marshal(legacyMsg) + if err != nil { + t.Fatalf("marshal legacy: %v", err) + } + var newMsg corev1.RewardMessage + if err := proto.Unmarshal(legacyBytes, &newMsg); err != nil { + t.Fatalf("unmarshal new shape: %v", err) + } + + signedTx := &corev1.SignedTransaction{ + Transaction: &corev1.SignedTransaction_Reward{Reward: &newMsg}, + } + + // Validate against an empty Server — we only need the dispatch to run far + // enough to confirm legacy bytes are rejected before any DB access. + s := &Server{} + err = s.isValidRewardTransaction(context.Background(), signedTx, 1) + if err == nil { + t.Fatalf("expected legacy reward tx to be rejected at validate-time") + } + if !strings.Contains(err.Error(), "legacy reward wire format is not accepted") { + t.Fatalf("expected legacy-rejection error, got: %v", err) + } +} + +func signLegacyCreate(t *testing.T, priv *ecdsa.PrivateKey, cr *corev1.LegacyCreateReward) string { + t.Helper() + data := common.LegacyDeterministicCreateRewardData(cr) + bytes, err := hex.DecodeString(data) + if err != nil { + t.Fatalf("decode hex: %v", err) + } + sig, err := common.EthSign(priv, bytes) + if err != nil { + t.Fatalf("sign: %v", err) + } + return sig +} + +func signLegacyDelete(t *testing.T, priv *ecdsa.PrivateKey, dr *corev1.LegacyDeleteReward) string { + t.Helper() + data := common.LegacyDeterministicDeleteRewardData(dr) + bytes, err := hex.DecodeString(data) + if err != nil { + t.Fatalf("decode hex: %v", err) + } + sig, err := common.EthSign(priv, bytes) + if err != nil { + t.Fatalf("sign: %v", err) + } + return sig +} diff --git a/pkg/core/server/state_sync.go b/pkg/core/server/state_sync.go index a9a904b4..b27e30c1 100644 --- a/pkg/core/server/state_sync.go +++ b/pkg/core/server/state_sync.go @@ -330,6 +330,8 @@ func (s *Server) createPgDump(logger *zap.Logger, latestSnapshotDir string) erro "core_parties", "core_deals", "core_rewards", + "core_reward_pools", + "launchpad_authority_rm", "core_uploads", "validator_history", } diff --git a/pkg/integration_tests/12_rewards_test.go b/pkg/integration_tests/12_rewards_test.go index cf93aca5..03e5c77e 100644 --- a/pkg/integration_tests/12_rewards_test.go +++ b/pkg/integration_tests/12_rewards_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "connectrpc.com/connect" v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" "github.com/OpenAudio/go-openaudio/pkg/integration_tests/utils" @@ -41,33 +42,44 @@ func TestRewardsLifecycle(t *testing.T) { t.Logf("creator key: %s", creatorAddr) t.Logf("deleter key: %s", deleterAddr) - // Step 1: Create two rewards with different claim authorities - // Reward 1: only creator as claim authority + // Under the pool-based model authorities live on the pool, not on + // the reward. To preserve the per-reward authority granularity of + // the original test (reward1: creator only; reward2: creator + + // deleter), we create two pools. + rmPubkeyA := freshSolanaPubkey(t) + rmPubkeyB := freshSolanaPubkey(t) + if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkeyA, + Authorities: []string{creatorAddr}, + }, 999999); err != nil { + t.Fatalf("Failed to create pool A: %v", err) + } + if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkeyB, + Authorities: []string{creatorAddr, deleterAddr}, + }, 999999); err != nil { + t.Fatalf("Failed to create pool B: %v", err) + } + + // Step 1: Create two rewards in different pools. reward1, err := creator.Rewards.CreateReward(ctx, &v1.CreateReward{ - RewardId: "reward1", - Name: "Test Reward 1", - Amount: 1000, - ClaimAuthorities: []*v1.ClaimAuthority{ - {Address: creatorAddr, Name: "Creator"}, - }, - DeadlineBlockHeight: 999999, - }) + RewardId: "reward1", + Name: "Test Reward 1", + Amount: 1000, + RewardsManagerPubkey: rmPubkeyA, + }, 999999) if err != nil { t.Fatalf("Failed to create reward1: %v", err) } t.Logf("Created reward1 at address: %s", reward1.Address) - // Reward 2: creator and deleter as claim authorities + // Reward 2: creator and deleter as claim authorities (via pool B). reward2, err := creator.Rewards.CreateReward(ctx, &v1.CreateReward{ - RewardId: "reward2", - Name: "Test Reward 2", - Amount: 2000, - ClaimAuthorities: []*v1.ClaimAuthority{ - {Address: creatorAddr, Name: "Creator"}, - {Address: deleterAddr, Name: "Deleter"}, - }, - DeadlineBlockHeight: 999999, - }) + RewardId: "reward2", + Name: "Test Reward 2", + Amount: 2000, + RewardsManagerPubkey: rmPubkeyB, + }, 999999) if err != nil { t.Fatalf("Failed to create reward2: %v", err) } @@ -100,8 +112,7 @@ func TestRewardsLifecycle(t *testing.T) { // Step 3: Deleter deletes reward2 deleteHash, err := deleter.Rewards.DeleteReward(ctx, &v1.DeleteReward{ Address: reward2.Address, - DeadlineBlockHeight: 999999, - }) + }, 999999) if err != nil { t.Fatalf("Failed to delete reward2: %v", err) } @@ -135,7 +146,6 @@ func TestRewardsLifecycle(t *testing.T) { }) t.Run("Test Reward Attestations with Claim Authorities", func(t *testing.T) { - t.Skip("artist-coin attestations are temporarily disabled; see GetRewardAttestation in pkg/core/server/connect.go") // Generate random private keys for claim authorities authority1Key, err := crypto.GenerateKey() @@ -166,17 +176,20 @@ func TestRewardsLifecycle(t *testing.T) { t.Logf("authority2 address: %s", authority2Addr) t.Logf("unauthorized address: %s", unauthorizedAddr) - // Create a reward with authority1 and authority2 as claim authorities + // Create a pool with both authorities and a reward in it. + rmPubkey := freshSolanaPubkey(t) + if _, err := authority1.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{authority1Addr, authority2Addr}, + }, 999999); err != nil { + t.Fatalf("Failed to create pool: %v", err) + } reward, err := authority1.Rewards.CreateReward(ctx, &v1.CreateReward{ - RewardId: "attestation_test_reward", - Name: "Attestation Test Reward", - Amount: 5000, - ClaimAuthorities: []*v1.ClaimAuthority{ - {Address: authority1Addr, Name: "Authority 1"}, - {Address: authority2Addr, Name: "Authority 2"}, - }, - DeadlineBlockHeight: 999999, - }) + RewardId: "attestation_test_reward", + Name: "Attestation Test Reward", + Amount: 5000, + RewardsManagerPubkey: rmPubkey, + }, 999999) if err != nil { t.Fatalf("Failed to create reward: %v", err) } @@ -216,10 +229,13 @@ func TestRewardsLifecycle(t *testing.T) { } t.Logf("authority2 successfully got attestation: %s", attestation2.Attestation) - // Test 3: unauthorized user should NOT be able to get attestation + // Test 3: unauthorized user should NOT be able to get attestation. + // Amount must match the reward amount (5000) so that the call + // passes Validate and actually exercises the auth gate; otherwise + // it would fail at amount validation instead. _, err = unauthorized.Rewards.GetRewardAttestation(ctx, &v1.GetRewardAttestationRequest{ EthRecipientAddress: recipientAddr, - Amount: 1000, + Amount: 5000, RewardAddress: reward.Address, RewardId: "attestation_test_reward", Specifier: specifier, @@ -229,28 +245,37 @@ func TestRewardsLifecycle(t *testing.T) { if err == nil { t.Fatalf("unauthorized user should NOT be able to get attestation, but it succeeded") } + if got := connect.CodeOf(err); got != connect.CodePermissionDenied { + t.Fatalf("expected PermissionDenied for unauthorized user, got %v: %v", got, err) + } t.Logf("unauthorized user correctly failed to get attestation: %v", err) // Test 4: Verify authority1 cannot get attestation for a reward they're not authorized for - // Create another reward with only authority2 + // Create another pool with only authority2 + a reward in it. + rmPubkey2 := freshSolanaPubkey(t) + if _, err := authority2.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey2, + Authorities: []string{authority2Addr}, + }, 999999); err != nil { + t.Fatalf("Failed to create pool 2: %v", err) + } reward2, err := authority2.Rewards.CreateReward(ctx, &v1.CreateReward{ - RewardId: "attestation_test_reward_2", - Name: "Attestation Test Reward 2", - Amount: 3000, - ClaimAuthorities: []*v1.ClaimAuthority{ - {Address: authority2Addr, Name: "Authority 2"}, - }, - DeadlineBlockHeight: 999999, - }) + RewardId: "attestation_test_reward_2", + Name: "Attestation Test Reward 2", + Amount: 3000, + RewardsManagerPubkey: rmPubkey2, + }, 999999) if err != nil { t.Fatalf("Failed to create reward2: %v", err) } t.Logf("Created reward2 at address: %s", reward2.Address) - // authority1 should NOT be able to get attestation for reward2 + // authority1 should NOT be able to get attestation for reward2. + // Amount matches reward2 (3000) so the call reaches the auth gate + // rather than failing earlier at amount validation. _, err = authority1.Rewards.GetRewardAttestation(ctx, &v1.GetRewardAttestationRequest{ EthRecipientAddress: recipientAddr, - Amount: 500, + Amount: 3000, RewardAddress: reward2.Address, RewardId: "attestation_test_reward_2", Specifier: specifier, @@ -260,13 +285,15 @@ func TestRewardsLifecycle(t *testing.T) { if err == nil { t.Fatalf("authority1 should NOT be able to get attestation for reward2, but it succeeded") } + if got := connect.CodeOf(err); got != connect.CodePermissionDenied { + t.Fatalf("expected PermissionDenied for cross-pool authority, got %v: %v", got, err) + } t.Logf("authority1 correctly failed to get attestation for reward2: %v", err) t.Logf("All reward attestation tests passed successfully!") }) t.Run("Test with Amount Validation", func(t *testing.T) { - t.Skip("artist-coin attestations are temporarily disabled; see GetRewardAttestation in pkg/core/server/connect.go") // Generate a new claim authority key authorityKey, err := crypto.GenerateKey() @@ -285,16 +312,20 @@ func TestRewardsLifecycle(t *testing.T) { creator := sdk.NewOpenAudioSDK(nodeUrl) creator.SetPrivKey(creatorKey) - // Create a reward with specific amount + // Create a pool that names authority + a reward bound to it. + rmPubkey := freshSolanaPubkey(t) + if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{authorityAddr}, + }, 999999); err != nil { + t.Fatalf("Failed to create pool: %v", err) + } reward, err := creator.Rewards.CreateReward(ctx, &v1.CreateReward{ - RewardId: "amount_test", - Name: "Amount Test Reward", - Amount: 100, // Fixed amount - ClaimAuthorities: []*v1.ClaimAuthority{ - {Address: authorityAddr, Name: "Test Authority"}, - }, - DeadlineBlockHeight: 999999, - }) + RewardId: "amount_test", + Name: "Amount Test Reward", + Amount: 100, + RewardsManagerPubkey: rmPubkey, + }, 999999) if err != nil { t.Fatalf("Failed to create reward: %v", err) } diff --git a/pkg/integration_tests/13_reward_pools_test.go b/pkg/integration_tests/13_reward_pools_test.go new file mode 100644 index 00000000..e81c3ad2 --- /dev/null +++ b/pkg/integration_tests/13_reward_pools_test.go @@ -0,0 +1,291 @@ +package integration_tests + +import ( + "context" + "crypto/rand" + "strings" + "testing" + + v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/OpenAudio/go-openaudio/pkg/common" + "github.com/OpenAudio/go-openaudio/pkg/integration_tests/utils" + "github.com/OpenAudio/go-openaudio/pkg/sdk" + "github.com/ethereum/go-ethereum/crypto" + "github.com/mr-tron/base58/base58" +) + +// freshSolanaPubkey returns a random 32-byte value base58-encoded — a +// well-formed Solana pubkey for the validator's wire-shape check +// (validateRewardsManagerPubkey). The value doesn't have to correspond to a +// real Solana account because the test only exercises the OAP-side pool +// primitive; PR3's sender-attestation gate will eventually consult Solana, +// but PR2's transactions only care about the wire shape. +func freshSolanaPubkey(t *testing.T) string { + t.Helper() + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + t.Fatalf("rand: %v", err) + } + return base58.Encode(b[:]) +} + +// TestRewardPoolsLifecycle exercises the cometbft RewardPool transactions: +// pool creation, gating CreateReward on pool membership, rotating +// authorities via SetRewardPoolAuthorities, and verifying that a rotated-out +// signer is no longer accepted. +func TestRewardPoolsLifecycle(t *testing.T) { + ctx := context.Background() + nodeUrl := utils.DiscoveryOneRPC + + if err := utils.WaitForDevnetHealthy(); err != nil { + t.Fatalf("Devnet not ready: %v", err) + } + + // Each test run gets its own fresh RM pubkey so reruns don't collide on + // the pool's identity (uniqueness is enforced server-side). + rmPubkey := freshSolanaPubkey(t) + otherRmPubkey := freshSolanaPubkey(t) + + aliceKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("alice key: %v", err) + } + aliceAddr := common.PrivKeyToAddress(aliceKey) + alice := sdk.NewOpenAudioSDK(nodeUrl) + alice.SetPrivKey(aliceKey) + + bobKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("bob key: %v", err) + } + bobAddr := common.PrivKeyToAddress(bobKey) + bob := sdk.NewOpenAudioSDK(nodeUrl) + bob.SetPrivKey(bobKey) + + malloryKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("mallory key: %v", err) + } + mallory := sdk.NewOpenAudioSDK(nodeUrl) + mallory.SetPrivKey(malloryKey) + + t.Run("CreateRewardPool rejects non-pubkey rewards_manager_pubkey", func(t *testing.T) { + _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: "not-a-real-pubkey", + Authorities: []string{aliceAddr}, + }, 999999) + if err == nil { + t.Fatalf("expected non-pubkey rewards_manager_pubkey to be rejected") + } + }) + + t.Run("CreateRewardPool requires signer in initial authorities", func(t *testing.T) { + _, err := mallory.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: otherRmPubkey, + Authorities: []string{aliceAddr, bobAddr}, + }, 999999) + if err == nil { + t.Fatalf("expected CreateRewardPool to reject signer not in initial authorities") + } + if _, err := alice.Rewards.GetRewardPool(ctx, otherRmPubkey); err == nil { + t.Fatalf("rejected pool should not have been persisted") + } + }) + + t.Run("Alice creates the pool with [alice, bob]", func(t *testing.T) { + _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{aliceAddr, bobAddr}, + }, 999999) + if err != nil { + t.Fatalf("create pool: %v", err) + } + + pool, err := alice.Rewards.GetRewardPool(ctx, rmPubkey) + if err != nil { + t.Fatalf("get pool: %v", err) + } + if pool.RewardsManagerPubkey != rmPubkey { + t.Fatalf("rewards_manager_pubkey: got %q want %q", pool.RewardsManagerPubkey, rmPubkey) + } + if !containsFold(pool.Authorities, aliceAddr) || !containsFold(pool.Authorities, bobAddr) { + t.Fatalf("authorities missing alice/bob: %v", pool.Authorities) + } + }) + + t.Run("CreateRewardPool with duplicate rewards_manager_pubkey is rejected", func(t *testing.T) { + _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{aliceAddr}, + }, 999999) + if err == nil { + t.Fatalf("expected duplicate rewards_manager_pubkey to be rejected") + } + }) + + t.Run("Pool member can create a reward in the pool", func(t *testing.T) { + reward, err := bob.Rewards.CreateReward(ctx, &v1.CreateReward{ + RewardId: "pool-reward-1", + Name: "Pool Reward", + Amount: 500, + RewardsManagerPubkey: rmPubkey, + }, 999999) + if err != nil { + t.Fatalf("bob create reward: %v", err) + } + if reward.Address == "" { + t.Fatalf("expected reward address") + } + + // GetRewards must accept caller's checksum-case address (which is what + // common.PrivKeyToAddress returns) even though stored authorities are + // lowercased by CanonicalAuthorities. Guards against the case- + // sensitivity regression. + listed, err := bob.Rewards.GetRewards(ctx, bobAddr) + if err != nil { + t.Fatalf("get rewards by checksum-case bob: %v", err) + } + found := false + for _, r := range listed.Rewards { + if r.Address == reward.Address { + found = true + break + } + } + if !found { + t.Fatalf("expected pool-attached reward %s in GetRewards(%s); got %d rewards", reward.Address, bobAddr, len(listed.Rewards)) + } + }) + + t.Run("Non-member cannot create a reward in the pool", func(t *testing.T) { + _, err := mallory.Rewards.CreateReward(ctx, &v1.CreateReward{ + RewardId: "pool-reward-evil", + Name: "Evil Reward", + Amount: 9999, + RewardsManagerPubkey: rmPubkey, + }, 999999) + if err == nil { + t.Fatalf("expected non-member CreateReward to be rejected") + } + }) + + t.Run("CreateReward without rewards_manager_pubkey is rejected", func(t *testing.T) { + _, err := alice.Rewards.CreateReward(ctx, &v1.CreateReward{ + RewardId: "no-rm", + Name: "no rm", + Amount: 1, + }, 999999) + if err == nil { + t.Fatalf("expected CreateReward without rewards_manager_pubkey to be rejected") + } + }) + + t.Run("SetRewardPoolAuthorities cannot be called by non-member", func(t *testing.T) { + _, err := mallory.Rewards.SetRewardPoolAuthorities(ctx, &v1.SetRewardPoolAuthorities{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{aliceAddr}, + }, 999999) + if err == nil { + t.Fatalf("expected non-member SetRewardPoolAuthorities to be rejected") + } + }) + + t.Run("SetRewardPoolAuthorities cannot set empty list", func(t *testing.T) { + _, err := alice.Rewards.SetRewardPoolAuthorities(ctx, &v1.SetRewardPoolAuthorities{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{}, + }, 999999) + if err == nil { + t.Fatalf("expected empty authorities to be rejected") + } + }) + + t.Run("SetRewardPoolAuthorities rejects non-eth-address entries", func(t *testing.T) { + _, err := alice.Rewards.SetRewardPoolAuthorities(ctx, &v1.SetRewardPoolAuthorities{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{"not-an-address"}, + }, 999999) + if err == nil { + t.Fatalf("expected non-eth-address authority to be rejected (would orphan pool)") + } + }) + + t.Run("Rotate: alice removes bob, leaving only alice", func(t *testing.T) { + _, err := alice.Rewards.SetRewardPoolAuthorities(ctx, &v1.SetRewardPoolAuthorities{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{aliceAddr}, + }, 999999) + if err != nil { + t.Fatalf("rotate: %v", err) + } + pool, err := alice.Rewards.GetRewardPool(ctx, rmPubkey) + if err != nil { + t.Fatalf("get pool after rotate: %v", err) + } + if len(pool.Authorities) != 1 || !strings.EqualFold(pool.Authorities[0], aliceAddr) { + t.Fatalf("after rotate expected only alice; got %v", pool.Authorities) + } + }) + + t.Run("Bob can no longer create a reward in the pool after rotation", func(t *testing.T) { + _, err := bob.Rewards.CreateReward(ctx, &v1.CreateReward{ + RewardId: "pool-reward-stale", + Name: "Stale Reward", + Amount: 1, + RewardsManagerPubkey: rmPubkey, + }, 999999) + if err == nil { + t.Fatalf("expected rotated-out bob to be rejected") + } + }) + + // === PR3 sender attestation flow === + // + // Pool at rmPubkey now has authorities = [alice]; bob has been rotated out. + // Validator should: sign create for alice (current authority), refuse + // create for bob (not in pool), sign delete for bob (rotated out → ok to + // remove from Solana), refuse delete for alice (still authorized). + + t.Run("CreateSender attestation: pool member alice is signed", func(t *testing.T) { + resp, err := alice.Rewards.GetRewardSenderAttestation(ctx, aliceAddr, rmPubkey) + if err != nil { + t.Fatalf("expected create-sender attestation for current authority alice: %v", err) + } + if resp.Attestation == "" { + t.Fatalf("expected attestation string") + } + }) + + t.Run("CreateSender attestation: rotated-out bob is rejected", func(t *testing.T) { + _, err := alice.Rewards.GetRewardSenderAttestation(ctx, bobAddr, rmPubkey) + if err == nil { + t.Fatalf("expected create-sender attestation to be refused for rotated-out bob") + } + }) + + t.Run("DeleteSender attestation: rotated-out bob is signed", func(t *testing.T) { + resp, err := alice.Rewards.GetDeleteRewardSenderAttestation(ctx, bobAddr, rmPubkey) + if err != nil { + t.Fatalf("expected delete-sender attestation for rotated-out bob: %v", err) + } + if resp.Attestation == "" { + t.Fatalf("expected attestation string") + } + }) + + t.Run("DeleteSender attestation: current authority alice is rejected", func(t *testing.T) { + _, err := alice.Rewards.GetDeleteRewardSenderAttestation(ctx, aliceAddr, rmPubkey) + if err == nil { + t.Fatalf("expected delete-sender attestation to be refused for current authority alice") + } + }) +} + +func containsFold(haystack []string, needle string) bool { + for _, h := range haystack { + if strings.EqualFold(h, needle) { + return true + } + } + return false +} diff --git a/pkg/rewards/reward_pool.go b/pkg/rewards/reward_pool.go new file mode 100644 index 00000000..5d72e8f5 --- /dev/null +++ b/pkg/rewards/reward_pool.go @@ -0,0 +1,29 @@ +package rewards + +import ( + "sort" + "strings" +) + +// CanonicalAuthorities normalizes a list of eth addresses to the canonical +// form stored in core_reward_pools.authorities: lowercased, trimmed, +// deduplicated, non-empty entries, sorted ascending. Producers of pool +// authority sets canonicalize before write so containment checks against +// the gin index on authorities stay deterministic. +func CanonicalAuthorities(authorities []string) []string { + seen := make(map[string]struct{}, len(authorities)) + out := make([]string, 0, len(authorities)) + for _, a := range authorities { + a = strings.ToLower(strings.TrimSpace(a)) + if a == "" { + continue + } + if _, ok := seen[a]; ok { + continue + } + seen[a] = struct{}{} + out = append(out, a) + } + sort.Strings(out) + return out +} diff --git a/pkg/rewards/reward_pool_test.go b/pkg/rewards/reward_pool_test.go new file mode 100644 index 00000000..e3c321ff --- /dev/null +++ b/pkg/rewards/reward_pool_test.go @@ -0,0 +1,75 @@ +package rewards + +import ( + "testing" +) + +func TestCanonicalAuthorities(t *testing.T) { + tests := []struct { + name string + in []string + want []string + }{ + { + name: "empty input", + in: []string{}, + want: []string{}, + }, + { + name: "nil input", + in: nil, + want: []string{}, + }, + { + name: "drops empty and whitespace-only entries", + in: []string{"", "0xabc", " ", "\t", "0xdef"}, + want: []string{"0xabc", "0xdef"}, + }, + { + name: "lowercases mixed case", + in: []string{"0xABC", "0xDeF"}, + want: []string{"0xabc", "0xdef"}, + }, + { + name: "trims surrounding whitespace", + in: []string{" 0xabc ", "\t0xdef\n"}, + want: []string{"0xabc", "0xdef"}, + }, + { + name: "deduplicates after canonicalization", + in: []string{"0xABC", "0xabc", " 0xABC ", "0xdef"}, + want: []string{"0xabc", "0xdef"}, + }, + { + name: "sorts ascending", + in: []string{"0xdef", "0xabc", "0x123"}, + want: []string{"0x123", "0xabc", "0xdef"}, + }, + { + name: "all whitespace results in empty", + in: []string{" ", "\t", ""}, + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CanonicalAuthorities(tt.in) + if !equalStringSlices(got, tt.want) { + t.Fatalf("got %v, want %v", got, tt.want) + } + }) + } +} + +func equalStringSlices(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/pkg/sdk/rewards/rewards.go b/pkg/sdk/rewards/rewards.go index c1fee593..81111fd8 100644 --- a/pkg/sdk/rewards/rewards.go +++ b/pkg/sdk/rewards/rewards.go @@ -27,65 +27,68 @@ func (r *Rewards) SetPrivKey(privKey *ecdsa.PrivateKey) { r.privKey = privKey } -func (r *Rewards) CreateReward(ctx context.Context, cr *v1.CreateReward) (*v1.GetRewardResponse, error) { - sig, err := common.SignCreateReward(r.privKey, cr) +// signAndSendReward signs the body, wraps it in the RewardMessage envelope +// alongside the signature, and submits the resulting SignedTransaction. +func (r *Rewards) signAndSendReward(ctx context.Context, body *v1.RewardBody) (string, error) { + sig, err := common.ProtoSign(r.privKey, body) if err != nil { - return nil, err + return "", err } - cr.Signature = sig - tx := &v1.SendTransactionRequest{ Transaction: &v1.SignedTransaction{ Transaction: &v1.SignedTransaction_Reward{ - Reward: &v1.RewardMessage{ - Action: &v1.RewardMessage_Create{Create: cr}, - }, + Reward: &v1.RewardMessage{Body: body, Signature: sig}, }, }, } - - req := connect.NewRequest(tx) - resp, err := r.core.SendTransaction(ctx, req) - if err != nil { - return nil, err - } - - txhash := resp.Msg.Transaction.Hash - reward, err := r.core.GetReward(ctx, connect.NewRequest(&v1.GetRewardRequest{ - Txhash: txhash, - })) + resp, err := r.core.SendTransaction(ctx, connect.NewRequest(tx)) if err != nil { - return nil, err + return "", err } - - return reward.Msg, nil + return resp.Msg.GetTransaction().GetHash(), nil } -func (r *Rewards) DeleteReward(ctx context.Context, dr *v1.DeleteReward) (string, error) { - sig, err := common.SignDeleteReward(r.privKey, dr) +func (r *Rewards) signAndSendRewardPool(ctx context.Context, body *v1.RewardPoolBody) (string, error) { + sig, err := common.ProtoSign(r.privKey, body) if err != nil { return "", err } - dr.Signature = sig - tx := &v1.SendTransactionRequest{ Transaction: &v1.SignedTransaction{ - Transaction: &v1.SignedTransaction_Reward{ - Reward: &v1.RewardMessage{ - Action: &v1.RewardMessage_Delete{Delete: dr}, - }, + Transaction: &v1.SignedTransaction_RewardPool{ + RewardPool: &v1.RewardPoolMessage{Body: body, Signature: sig}, }, }, } - - req := connect.NewRequest(tx) - deleteRes, err := r.core.SendTransaction(ctx, req) + resp, err := r.core.SendTransaction(ctx, connect.NewRequest(tx)) if err != nil { return "", err } + return resp.Msg.GetTransaction().GetHash(), nil +} + +func (r *Rewards) CreateReward(ctx context.Context, cr *v1.CreateReward, deadlineBlockHeight int64) (*v1.GetRewardResponse, error) { + body := &v1.RewardBody{ + DeadlineBlockHeight: deadlineBlockHeight, + Action: &v1.RewardBody_Create{Create: cr}, + } + txhash, err := r.signAndSendReward(ctx, body) + if err != nil { + return nil, err + } + reward, err := r.core.GetReward(ctx, connect.NewRequest(&v1.GetRewardRequest{Txhash: txhash})) + if err != nil { + return nil, err + } + return reward.Msg, nil +} - txhash := deleteRes.Msg.GetTransaction().GetHash() - return txhash, nil +func (r *Rewards) DeleteReward(ctx context.Context, dr *v1.DeleteReward, deadlineBlockHeight int64) (string, error) { + body := &v1.RewardBody{ + DeadlineBlockHeight: deadlineBlockHeight, + Action: &v1.RewardBody_Delete{Delete: dr}, + } + return r.signAndSendReward(ctx, body) } func (r *Rewards) GetReward(ctx context.Context, address string) (*v1.GetRewardResponse, error) { @@ -113,6 +116,75 @@ func (r *Rewards) GetRewards(ctx context.Context, claim_authority string) (*v1.G return resp.Msg, nil } +// CreateRewardPool submits a CreateRewardPool transaction. The pool is +// keyed by msg.RewardsManagerPubkey (the Solana reward manager pubkey it +// will govern, base58 32 bytes); subsequent SetRewardPoolAuthorities / +// CreateReward calls reference the pool by this same value, so callers +// can compose dependent transactions without round-tripping through +// GetRewardPool. Returns the cometbft tx hash. +func (r *Rewards) CreateRewardPool(ctx context.Context, msg *v1.CreateRewardPool, deadlineBlockHeight int64) (string, error) { + body := &v1.RewardPoolBody{ + DeadlineBlockHeight: deadlineBlockHeight, + Action: &v1.RewardPoolBody_Create{Create: msg}, + } + return r.signAndSendRewardPool(ctx, body) +} + +// GetRewardPool fetches a pool by its rewards_manager_pubkey (Solana RM +// pubkey, base58, 32 bytes). +func (r *Rewards) GetRewardPool(ctx context.Context, rewardsManagerPubkey string) (*v1.GetRewardPoolResponse, error) { + resp, err := r.core.GetRewardPool(ctx, connect.NewRequest(&v1.GetRewardPoolRequest{RewardsManagerPubkey: rewardsManagerPubkey})) + if err != nil { + return nil, err + } + return resp.Msg, nil +} + +// SetRewardPoolAuthorities replaces the pool's authority set wholesale. The +// caller composes the desired list (current minus the one to remove, current +// plus the one to add, etc.); add and remove are derived views. +func (r *Rewards) SetRewardPoolAuthorities(ctx context.Context, msg *v1.SetRewardPoolAuthorities, deadlineBlockHeight int64) (string, error) { + body := &v1.RewardPoolBody{ + DeadlineBlockHeight: deadlineBlockHeight, + Action: &v1.RewardPoolBody_SetAuthorities{SetAuthorities: msg}, + } + return r.signAndSendRewardPool(ctx, body) +} + +// GetRewardSenderAttestation requests an attestation that the validator will +// sign authorizing addr to be added as a sender on the Solana reward manager +// account identified by rewardsManagerPubkey. For pool-managed RMs, the +// validator signs iff addr ∈ pool.authorities; for unmanaged RMs (notably +// AUDIO), the validator falls back to its validator/AAO trust set. The +// returned attestation is meant to be combined with attestations from other +// validators and submitted to Solana via CreateSenderPublic. +func (r *Rewards) GetRewardSenderAttestation(ctx context.Context, addr string, rewardsManagerPubkey string) (*v1.GetRewardSenderAttestationResponse, error) { + resp, err := r.core.GetRewardSenderAttestation(ctx, connect.NewRequest(&v1.GetRewardSenderAttestationRequest{ + Address: addr, + RewardsManagerPubkey: rewardsManagerPubkey, + })) + if err != nil { + return nil, err + } + return resp.Msg, nil +} + +// GetDeleteRewardSenderAttestation is the inverse of GetRewardSenderAttestation: +// the validator signs an attestation authorizing the removal of addr as a +// sender on the Solana RM. For pool-managed RMs, the validator signs iff +// addr ∉ pool.authorities (proving OAP-side rotation already happened). +// Used to deregister leaked / rotated-out keys from Solana. +func (r *Rewards) GetDeleteRewardSenderAttestation(ctx context.Context, addr string, rewardsManagerPubkey string) (*v1.GetDeleteRewardSenderAttestationResponse, error) { + resp, err := r.core.GetDeleteRewardSenderAttestation(ctx, connect.NewRequest(&v1.GetDeleteRewardSenderAttestationRequest{ + Address: addr, + RewardsManagerPubkey: rewardsManagerPubkey, + })) + if err != nil { + return nil, err + } + return resp.Msg, nil +} + func (r *Rewards) GetRewardAttestation(ctx context.Context, req *v1.GetRewardAttestationRequest) (*v1.GetRewardAttestationResponse, error) { // Create a RewardClaim to compile the data in the correct format claim := pkgrewards.RewardClaim{ diff --git a/proto/core/v1/service.proto b/proto/core/v1/service.proto index c7764930..6975e98b 100644 --- a/proto/core/v1/service.proto +++ b/proto/core/v1/service.proto @@ -27,6 +27,7 @@ service CoreService { rpc GetReward(GetRewardRequest) returns (GetRewardResponse) {} rpc GetRewards(GetRewardsRequest) returns (GetRewardsResponse) {} + rpc GetRewardPool(GetRewardPoolRequest) returns (GetRewardPoolResponse) {} rpc GetRewardAttestation(GetRewardAttestationRequest) returns (GetRewardAttestationResponse) {} rpc GetRewardSenderAttestation(GetRewardSenderAttestationRequest) returns (GetRewardSenderAttestationResponse) {} rpc GetDeleteRewardSenderAttestation(GetDeleteRewardSenderAttestationRequest) returns (GetDeleteRewardSenderAttestationResponse) {} diff --git a/proto/core/v1/types.proto b/proto/core/v1/types.proto index cb2dd3a0..2a297a2b 100644 --- a/proto/core/v1/types.proto +++ b/proto/core/v1/types.proto @@ -288,6 +288,7 @@ message SignedTransaction { ddex.v1beta1.NewReleaseMessage release = 1008; RewardMessage reward = 1009; FileUpload file_upload = 1010; + RewardPoolMessage reward_pool = 1011; } } @@ -513,7 +514,21 @@ message GetPIEResponse { ddex.v1beta1.PieMessage pie = 1; } +// RewardMessage is the wire envelope: it bundles a signed body with its +// signature. The body is what gets signed (deterministic protobuf bytes); +// the signature lives alongside the body, not inside it, so signing has no +// chicken-and-egg. message RewardMessage { + RewardBody body = 1; + string signature = 2; // signature over the deterministic marshaling of body +} + +// RewardBody is the signed payload. deadline_block_height bounds the +// signature's validity (the validator rejects after expiry); the action is +// the actual operation. The oneof tag discriminates Create from Delete so +// two actions with otherwise-identical inner shapes produce different bytes. +message RewardBody { + int64 deadline_block_height = 1; oneof action { CreateReward create = 1000; DeleteReward delete = 1001; @@ -524,15 +539,112 @@ message CreateReward { string reward_id = 1; string name = 2; uint64 amount = 3; - repeated ClaimAuthority claim_authorities = 4; - int64 deadline_block_height = 5; - string signature = 6; // Signature over deterministic reward data + // Tags 4–6 are reserved: previously held claim_authorities (4), + // deadline_block_height (5), and signature (6) on the pre-pool + // CreateReward shape. The wire-compat layer routes legacy bytes + // through LegacyCreateReward (which preserves those tags), so the + // new CreateReward never decodes legacy data — but reusing those + // tag numbers for new fields would still be a footgun for any + // future tool that bypasses the wire-compat layer or any refactor + // that drops it. Reserve them. + reserved 4, 5, 6; + reserved "claim_authorities", "deadline_block_height", "signature"; + // Solana reward manager pubkey (base58, 32 bytes). Identifies the pool + // whose authorities are allowed to issue rewards in this RM. The + // recovered signer must be a current member of pool.authorities; the + // reward inherits attestation rights from the pool going forward. + // The pool itself is identified by this same pubkey: pool address == + // rewards_manager_pubkey for first-class pools. + string rewards_manager_pubkey = 7; } message DeleteReward { + // Tags 2–3 are reserved: previously held deadline_block_height (2) + // and signature (3) on the pre-pool DeleteReward shape (now + // preserved on LegacyDeleteReward). + reserved 2, 3; + reserved "deadline_block_height", "signature"; string address = 1; // The deployed reward address +} + +// RewardPoolMessage is the wire envelope for pool-management transactions. +// Same shape as RewardMessage: body + signature. +message RewardPoolMessage { + RewardPoolBody body = 1; + string signature = 2; +} + +message RewardPoolBody { + int64 deadline_block_height = 1; + oneof action { + CreateRewardPool create = 2000; + SetRewardPoolAuthorities set_authorities = 2001; + } +} + +message CreateRewardPool { + // The pool is identified by its Solana reward manager pubkey (base58, + // 32 bytes). Subsequent CreateReward / SetRewardPoolAuthorities messages + // and PR3's sender-attestation gate use this same value to resolve the + // pool. There is no separate "pool address" — the pool's identity IS the + // RM pubkey, so pool↔RM binding cannot be set wrong by construction. + string rewards_manager_pubkey = 1; + // Initial set of eth addresses authorized to attest for rewards in this + // pool. The recovered signer must be a member of this list. Each entry + // must be a valid eth hex address. + repeated string authorities = 2; +} + +// SetRewardPoolAuthorities replaces the pool's authority set wholesale. The +// signer must be in the *current* pool.authorities; the new list must be +// non-empty and contain only valid eth addresses (otherwise the pool is +// permanently orphaned). Add and remove are derived views: callers compose +// the new list themselves. +message SetRewardPoolAuthorities { + string rewards_manager_pubkey = 1; + repeated string authorities = 2; +} + +// === Legacy reward wire format (pre-pool-rollout) === +// +// These messages are the on-the-wire shape used before the body+signature +// envelope was introduced. They are NOT used by new clients or new code paths +// — they exist solely so that the new binary can decode and apply historical +// reward transactions encountered during block-sync-from-genesis. +// +// DO NOT REMOVE these types or their field tags: removing them would break +// replay of any chain history that contains rewards committed before the +// upgrade. Adding new fields here is also forbidden — the wire shape must +// remain pinned to what the old network produced. +message LegacyRewardMessage { + oneof action { + LegacyCreateReward create = 1000; + LegacyDeleteReward delete = 1001; + } +} + +message LegacyCreateReward { + string reward_id = 1; + string name = 2; + uint64 amount = 3; + repeated ClaimAuthority claim_authorities = 4; + int64 deadline_block_height = 5; + string signature = 6; +} + +message LegacyDeleteReward { + string address = 1; int64 deadline_block_height = 2; - string signature = 3; // Signature over deterministic delete data + string signature = 3; +} + +message GetRewardPoolRequest { + string rewards_manager_pubkey = 1; +} + +message GetRewardPoolResponse { + string rewards_manager_pubkey = 1; + repeated string authorities = 2; } message GetRewardRequest { From 4a5ea4e891276bbfe2251566f2ceb9cf9c73fb0f Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 8 May 2026 02:08:01 -0700 Subject: [PATCH 2/9] Tighten reward-pool gating: union replay, AUDIO-only fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three review-driven fixes on the bundle branch: 1. Replay/migration apphash divergence (#1). UpsertSyntheticRewardPool was a hard overwrite, which produced pool.authorities = last-replayed-reward.authorities on a from-genesis block-sync — diverging from the migration backfill, which UNIONs authorities across every legacy reward referencing the RM. Production data has at most one authority per reward today, so the bug doesn't currently manifest, but it's cheap insurance against future drift (multi-authority rewards, debug keys, etc.). The DO UPDATE clause now unions existing pool authorities with the incoming set. Renamed the query to UpsertLegacyReplayRewardPool to reflect its actual (and only) caller — the mig_ shape was already gone (#5). 2. senderGateForRM AUDIO-only fallback (#2). The legacy validator/AAO trust set used to be the fallback for ANY RM without a pool. That was a quietly-permissive seam — any caller could request validator-signed attestations for an arbitrary unknown RM. Now the fallback applies only when the requested RM equals the configured AUDIO RM; every other no-pool RM gets ErrSenderGateUnknownRM, which the handlers map to InvalidArgument. 3. Stale doc comment in rewards_legacy.go (#6) saying the file did "synthetic-pool fallback for create" — predates the mig_ removal. Updated to describe the launchpad-lookup behavior. Co-Authored-By: Claude Opus 4.7 --- pkg/core/db/sql/writes.sql | 29 +++++++++----- pkg/core/db/writes.sql.go | 65 ++++++++++++++++++------------- pkg/core/server/connect.go | 63 +++++++++++++++++++++--------- pkg/core/server/rewards_legacy.go | 16 +++++--- 4 files changed, 113 insertions(+), 60 deletions(-) diff --git a/pkg/core/db/sql/writes.sql b/pkg/core/db/sql/writes.sql index 77ea26b7..bd5b70dc 100644 --- a/pkg/core/db/sql/writes.sql +++ b/pkg/core/db/sql/writes.sql @@ -356,20 +356,31 @@ where address = $1; delete from core_rewards where address = $1; --- name: UpsertSyntheticRewardPool :exec --- Used by the legacy CreateReward path to ensure a reward-pool row exists --- for the provided rewards_manager_pubkey with the requested authority --- set. The pubkey may be a real Solana RM (when the row's --- claim_authorities included a known launchpad-derived per-mint key) or --- a synthetic 'mig_' identifier (otherwise). DO UPDATE refreshes --- the stored authorities so multiple CreateReward txs targeting the --- same pool converge instead of leaving stale rows. +-- name: UpsertLegacyReplayRewardPool :exec +-- Used only by finalizeLegacyCreateReward during block-sync replay of +-- legacy-shape CreateReward bytes. Materializes a real-RM pool from the +-- legacy reward's inline claim_authorities. +-- +-- DO UPDATE unions the new authorities with the existing set rather than +-- overwriting. This matches the migration backfill, which UNIONs authorities +-- across every legacy reward referencing the RM. Without the union, a +-- from-genesis-syncing node would end up with +-- pool.authorities = last-replayed-reward.authorities, while an in-place- +-- upgraded node would have UNION(every reward) — different DB state ⇒ +-- different validation outcomes for downstream txs ⇒ apphash divergence. insert into core_reward_pools ( rewards_manager_pubkey, authorities ) values ($1, $2) on conflict (rewards_manager_pubkey) do update set - authorities = excluded.authorities, + authorities = ( + select array( + select distinct lower(trim(a)) + from unnest(core_reward_pools.authorities || excluded.authorities) as a + where a is not null and trim(a) <> '' + order by 1 + ) + ), updated_at = now(); -- name: InsertRewardPool :exec diff --git a/pkg/core/db/writes.sql.go b/pkg/core/db/writes.sql.go index bf57627b..09418160 100644 --- a/pkg/core/db/writes.sql.go +++ b/pkg/core/db/writes.sql.go @@ -1161,6 +1161,44 @@ func (q *Queries) UpsertAppState(ctx context.Context, arg UpsertAppStateParams) return err } +const upsertLegacyReplayRewardPool = `-- name: UpsertLegacyReplayRewardPool :exec +insert into core_reward_pools ( + rewards_manager_pubkey, + authorities +) values ($1, $2) +on conflict (rewards_manager_pubkey) do update set + authorities = ( + select array( + select distinct lower(trim(a)) + from unnest(core_reward_pools.authorities || excluded.authorities) as a + where a is not null and trim(a) <> '' + order by 1 + ) + ), + updated_at = now() +` + +type UpsertLegacyReplayRewardPoolParams struct { + RewardsManagerPubkey string + Authorities []string +} + +// Used only by finalizeLegacyCreateReward during block-sync replay of +// legacy-shape CreateReward bytes. Materializes a real-RM pool from the +// legacy reward's inline claim_authorities. +// +// DO UPDATE unions the new authorities with the existing set rather than +// overwriting. This matches the migration backfill, which UNIONs authorities +// across every legacy reward referencing the RM. Without the union, a +// from-genesis-syncing node would end up with +// pool.authorities = last-replayed-reward.authorities, while an in-place- +// upgraded node would have UNION(every reward) — different DB state ⇒ +// different validation outcomes for downstream txs ⇒ apphash divergence. +func (q *Queries) UpsertLegacyReplayRewardPool(ctx context.Context, arg UpsertLegacyReplayRewardPoolParams) error { + _, err := q.db.Exec(ctx, upsertLegacyReplayRewardPool, arg.RewardsManagerPubkey, arg.Authorities) + return err +} + const upsertSlaRollupReport = `-- name: UpsertSlaRollupReport :exec with updated as ( update sla_node_reports @@ -1177,30 +1215,3 @@ func (q *Queries) UpsertSlaRollupReport(ctx context.Context, address string) err _, err := q.db.Exec(ctx, upsertSlaRollupReport, address) return err } - -const upsertSyntheticRewardPool = `-- name: UpsertSyntheticRewardPool :exec -insert into core_reward_pools ( - rewards_manager_pubkey, - authorities -) values ($1, $2) -on conflict (rewards_manager_pubkey) do update set - authorities = excluded.authorities, - updated_at = now() -` - -type UpsertSyntheticRewardPoolParams struct { - RewardsManagerPubkey string - Authorities []string -} - -// Used by the legacy CreateReward path to ensure a reward-pool row exists -// for the provided rewards_manager_pubkey with the requested authority -// set. The pubkey may be a real Solana RM (when the row's -// claim_authorities included a known launchpad-derived per-mint key) or -// a synthetic 'mig_' identifier (otherwise). DO UPDATE refreshes -// the stored authorities so multiple CreateReward txs targeting the -// same pool converge instead of leaving stale rows. -func (q *Queries) UpsertSyntheticRewardPool(ctx context.Context, arg UpsertSyntheticRewardPoolParams) error { - _, err := q.db.Exec(ctx, upsertSyntheticRewardPool, arg.RewardsManagerPubkey, arg.Authorities) - return err -} diff --git a/pkg/core/server/connect.go b/pkg/core/server/connect.go index 182dbb33..73cce847 100644 --- a/pkg/core/server/connect.go +++ b/pkg/core/server/connect.go @@ -1791,6 +1791,11 @@ func (c *CoreService) gatherEligibleSenderAddresses(ctx context.Context) ([]stri return append(validators, aaos...), nil } +// ErrSenderGateUnknownRM is returned by senderGateForRM when the requested +// RM has no core_reward_pools row AND is not the configured AUDIO RM. +// Callers should map it to connect.CodeInvalidArgument. +var ErrSenderGateUnknownRM = errors.New("rewards_manager_pubkey has no pool and is not AUDIO") + // senderGateForRM resolves which gating regime applies to a given Solana // reward manager pubkey: // @@ -1801,33 +1806,47 @@ func (c *CoreService) gatherEligibleSenderAddresses(ctx context.Context) ([]stri // asked to deregister it from Solana, and once a new key is rotated in, // validators can register it. // -// - If no pool exists for the RM, fall through to the legacy -// validator/AAO trust set (see gatherEligibleSenderAddresses). AUDIO -// deliberately stays on this path: no pool is ever created for the -// AUDIO RM (validateCreateRewardPool refuses), so AUDIO attestation -// authority remains the network-wide trust set. +// - If no pool exists AND the RM is the configured AUDIO RM, fall through +// to the legacy validator/AAO trust set (see +// gatherEligibleSenderAddresses). AUDIO is the only RM that may use +// this path: no pool is ever created for it (validateCreateRewardPool +// refuses), so AUDIO attestation authority remains the network-wide +// trust set. +// +// - If no pool exists AND the RM is NOT AUDIO, return +// ErrSenderGateUnknownRM. We deliberately refuse to fall through — +// pools are now the only authorization mechanism for non-AUDIO RMs, +// and a quietly-permissive fallback would re-open the exact gap the +// pool primitive is meant to close (any caller could request +// validator-signed attestations for an arbitrary unknown RM). // -// Returns (pool, true, nil) when pool gating applies, (nil, false, nil) -// when validator/AAO gating applies, and a non-nil error only on real DB -// failures (transient errors do NOT silently fall through to validator/AAO -// — that would let a temporary blip downgrade the gate from per-RM to -// network-wide). +// Returns (pool, true, nil) for pool gating, (nil, false, nil) for the +// AUDIO legacy path, ErrSenderGateUnknownRM for any other no-pool RM, and +// other errors only on real DB failures (transient errors do NOT silently +// fall through to validator/AAO — that would let a temporary blip +// downgrade the gate). func (c *CoreService) senderGateForRM(ctx context.Context, rmPubkey string) (*db.CoreRewardPool, bool, error) { pool, err := c.core.db.GetRewardPool(ctx, rmPubkey) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, false, nil - } + switch { + case err == nil: + return &pool, true, nil + case !errors.Is(err, pgx.ErrNoRows): return nil, false, fmt.Errorf("failed to load pool for RM %s: %w", rmPubkey, err) } - return &pool, true, nil + if audioRM := strings.TrimSpace(config.AudioRewardsManagerPubkey()); audioRM != "" && rmPubkey == audioRM { + return nil, false, nil + } + return nil, false, ErrSenderGateUnknownRM } // GetRewardSenderAttestation implements v1connect.CoreServiceHandler. // // Dispatches by RM: // - If a pool exists for the requested RM, sign iff address ∈ pool.authorities. -// - Otherwise, sign iff address ∈ validator/AAO trust set (legacy AUDIO path). +// - If no pool exists and the RM is the configured AUDIO RM, sign iff +// address ∈ validator/AAO trust set (legacy AUDIO path). +// - Otherwise (no pool, not AUDIO), refuse — there is no authorization +// mechanism to consult. func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *connect.Request[v1.GetRewardSenderAttestationRequest]) (*connect.Response[v1.GetRewardSenderAttestationResponse], error) { address := strings.TrimSpace(req.Msg.Address) if address == "" { @@ -1841,6 +1860,9 @@ func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *conne pool, isPoolGated, err := c.senderGateForRM(ctx, rewardsManagerPubkey) if err != nil { + if errors.Is(err, ErrSenderGateUnknownRM) { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } return nil, connect.NewError(connect.CodeInternal, err) } @@ -1881,8 +1903,10 @@ func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *conne // - If a pool exists for the requested RM, sign iff address ∉ pool.authorities. // This is the rotation-out signal: OAP has already removed the key from the // pool, and validators are catching Solana up. -// - Otherwise (AUDIO path), sign iff address is NOT in the validator/AAO -// trust set (existing behavior). +// - If no pool exists and the RM is the configured AUDIO RM, sign iff +// address is NOT in the validator/AAO trust set (legacy AUDIO path). +// - Otherwise (no pool, not AUDIO), refuse — there is no authorization +// mechanism to consult. func (c *CoreService) GetDeleteRewardSenderAttestation(ctx context.Context, req *connect.Request[v1.GetDeleteRewardSenderAttestationRequest]) (*connect.Response[v1.GetDeleteRewardSenderAttestationResponse], error) { address := strings.TrimSpace(req.Msg.Address) if address == "" { @@ -1896,6 +1920,9 @@ func (c *CoreService) GetDeleteRewardSenderAttestation(ctx context.Context, req pool, isPoolGated, err := c.senderGateForRM(ctx, rewardsManagerPubkey) if err != nil { + if errors.Is(err, ErrSenderGateUnknownRM) { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } return nil, connect.NewError(connect.CodeInternal, err) } diff --git a/pkg/core/server/rewards_legacy.go b/pkg/core/server/rewards_legacy.go index af5222a6..e7b7f723 100644 --- a/pkg/core/server/rewards_legacy.go +++ b/pkg/core/server/rewards_legacy.go @@ -27,9 +27,12 @@ import ( // during block-sync-from-genesis. We re-decode the unknown fields as // LegacyRewardMessage, recover the signer using the legacy signing scheme // (sha256 over a pipe-delimited canonical string — see -// pkg/common/legacy_reward_signing.go), and apply the same business logic -// the new code applies to a CreateReward / DeleteReward without pool_address -// (synthetic-pool fallback for create; claim-authorities check for delete). +// pkg/common/legacy_reward_signing.go), and bind the reward to a real-RM +// pool by looking up any one of its inline claim_authorities in the +// launchpad_authority_rm seed (matching the migration backfill exactly). +// For DeleteReward we re-check authorization against the existing reward's +// pool authorities. Rewards whose authorities don't match any launchpad +// entry are inserted with NULL rewards_manager_pubkey. // // Live-traffic note: post-rollout, no client should produce legacy bytes — // the SDK and API repo only emit the new envelope. This path is dormant @@ -150,9 +153,10 @@ func (s *Server) finalizeLegacyCreateReward(ctx context.Context, req *abcitypes. case err != nil: return fmt.Errorf("failed to resolve launchpad RM: %w", err) default: - // Upsert the pool so any subsequent legacy replays of rewards - // targeting the same RM converge on the same authority set. - if err := qtx.UpsertSyntheticRewardPool(ctx, db.UpsertSyntheticRewardPoolParams{ + // Upsert (UNION) the pool so successive legacy replays of + // rewards under the same RM accumulate into the same authority + // set the migration's UNION-based backfill produces. + if err := qtx.UpsertLegacyReplayRewardPool(ctx, db.UpsertLegacyReplayRewardPoolParams{ RewardsManagerPubkey: rm, Authorities: canonicalAuthorities, }); err != nil { From e3c14116138909ca31e51fe59cba85cb222f38ca Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 8 May 2026 17:58:50 -0700 Subject: [PATCH 3/9] CreateRewardPool: require ed25519 signature from RM keypair MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the pool-creation frontrunning vector. Today's validateCreateRewardPool only requires signer ∈ initial_authorities, which an attacker satisfies trivially by listing themselves. After a new reward manager is initialized on Solana, an observer who watches init events can race the legitimate launchpad operator's CreateRewardPool and register a pool with attacker-chosen authorities; the legitimate operator is then locked out (PK conflict on rewards_manager_pubkey), and the attacker controls every reward and sender attestation under the RM. Defense rests on a property of the existing system: the Solana rewardManagerState account is a deterministic ed25519 keypair, derived by the launchpad relay as Keypair.fromSeed(sha256(launchpadDeterministicSecret || 'audius-launchpad' || 'reward-manager' || mint)) (see apps/.../solana-relay/.../launchpad/launch_coin.ts). The launchpad has the secret and can re-derive the keypair at will; an attacker who lacks the secret cannot. The 32-byte rewardManagerState public key IS what cometbft has been carrying as rewards_manager_pubkey — so we already have an ed25519 verification key in hand at validate time. This commit: 1. Adds CreateRewardPool.rm_owner_signature (proto tag 3, bytes). 2. Defines a canonical signing payload in pkg/rewards: "audius:create-reward-pool:" + chain_id + ":" + rm_pubkey_b58 + ":" + sorted_lowercased_authorities.join(",") and a SignCreateRewardPool helper for client-side use. 3. validateCreateRewardPool and finalizeCreateRewardPool each call verifyRewardPoolOwnerSignature, which decodes rm_pubkey from base58 and runs ed25519.Verify against the canonical payload. Defense-in-depth at finalize matches the existing pattern for replay-time invariants. 4. Updates SDK example (examples/rewards/main.go) and integration tests to populate the signature. New unit tests cover positive verification, canonicalization invariance, foreign-keypair rejection, cross-chain replay, mismatched authorities, malformed signature length, and rm_pubkey shape errors. The existing signer ∈ initial_authorities check is retained alongside the new ed25519 gate. They're independent: the ed25519 sig proves control of the RM keypair (frontrunning defense); the membership check enforces the existing "you can't create a pool you have no membership in" property. Operationally, the launchpad relay holds both the per-mint claim authority eth key (envelope signer + the only initial authority) and the RM ed25519 keypair, so producing both signatures is symmetric. Co-Authored-By: Claude Opus 4.7 --- examples/rewards/main.go | 36 +- pkg/api/core/v1/types.pb.go | 413 +++++++++--------- pkg/core/server/reward_pools.go | 64 ++- pkg/core/server/reward_pools_test.go | 94 ++++ pkg/integration_tests/12_rewards_test.go | 59 +-- pkg/integration_tests/13_reward_pools_test.go | 99 +++-- pkg/rewards/reward_pool.go | 44 ++ proto/core/v1/types.proto | 11 + 8 files changed, 564 insertions(+), 256 deletions(-) diff --git a/examples/rewards/main.go b/examples/rewards/main.go index 390b5e21..e524ee13 100644 --- a/examples/rewards/main.go +++ b/examples/rewards/main.go @@ -2,6 +2,8 @@ package main import ( "context" + "crypto/ed25519" + "encoding/hex" "fmt" "log" "os" @@ -9,7 +11,9 @@ import ( "connectrpc.com/connect" v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" + "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/OpenAudio/go-openaudio/pkg/sdk" + "github.com/mr-tron/base58/base58" ) func main() { @@ -29,6 +33,9 @@ func main() { oap := sdk.NewOpenAudioSDK("creatornode11.staging.audius.co") oap.SetPrivKey(privateKey) + if err := oap.Init(context.Background()); err != nil { + log.Fatalf("Failed to init SDK: %v", err) + } resp, err := oap.Core.GetStatus(context.Background(), connect.NewRequest(&v1.GetStatusRequest{})) if err != nil { @@ -38,12 +45,27 @@ func main() { currentHeight := resp.Msg.ChainInfo.CurrentHeight deadline := currentHeight + 100 - // Replace with the actual Solana reward manager pubkey for the mint - // you're issuing rewards under (base58, 32 bytes). - rewardsManagerPubkey := os.Getenv("REWARDS_MANAGER_PUBKEY") - if rewardsManagerPubkey == "" { - log.Fatalf("REWARDS_MANAGER_PUBKEY environment variable is not set") + // CreateRewardPool requires possession of the RM's ed25519 keypair — + // the same keypair that signed the InitRewardManager instruction on + // Solana. The launchpad relay derives this from + // (launchpadDeterministicSecret, mint) and so always has it. + // + // REWARDS_MANAGER_SECRET_HEX is the 64-byte ed25519 secret key, hex- + // encoded. We derive the public key (which IS the rewards_manager_pubkey, + // base58-encoded) from it. + secretHex := os.Getenv("REWARDS_MANAGER_SECRET_HEX") + if secretHex == "" { + log.Fatalf("REWARDS_MANAGER_SECRET_HEX environment variable is not set (64-byte ed25519 secret, hex-encoded)") + } + rmSecret, err := hex.DecodeString(secretHex) + if err != nil { + log.Fatalf("Failed to decode REWARDS_MANAGER_SECRET_HEX: %v", err) + } + if len(rmSecret) != ed25519.PrivateKeySize { + log.Fatalf("REWARDS_MANAGER_SECRET_HEX must decode to %d bytes; got %d", ed25519.PrivateKeySize, len(rmSecret)) } + rmPrivKey := ed25519.PrivateKey(rmSecret) + rewardsManagerPubkey := base58.Encode(rmPrivKey.Public().(ed25519.PublicKey)) // First-class CreateReward requires an existing pool keyed by the // reward manager pubkey. Create one — fail loudly on any error so the @@ -51,9 +73,11 @@ func main() { // created in a previous run, this will surface as a "pool already // exists" error and the example needs to be rerun against a fresh RM // pubkey (or the existing pool's tx hash recorded for the reuse path). + authorities := []string{oap.Address()} if _, err := oap.Rewards.CreateRewardPool(context.Background(), &v1.CreateRewardPool{ RewardsManagerPubkey: rewardsManagerPubkey, - Authorities: []string{oap.Address()}, + Authorities: authorities, + RmOwnerSignature: rewards.SignCreateRewardPool(rmPrivKey, oap.ChainID(), rewardsManagerPubkey, authorities), }, deadline); err != nil { log.Fatalf("Failed to create reward pool: %v", err) } diff --git a/pkg/api/core/v1/types.pb.go b/pkg/api/core/v1/types.pb.go index 8281ca5a..25b42902 100644 --- a/pkg/api/core/v1/types.pb.go +++ b/pkg/api/core/v1/types.pb.go @@ -4547,6 +4547,19 @@ type CreateRewardPool struct { // pool. The recovered signer must be a member of this list. Each entry // must be a valid eth hex address. Authorities []string `protobuf:"bytes,2,rep,name=authorities,proto3" json:"authorities,omitempty"` + // ed25519 signature over the canonical payload + // + // "audius:create-reward-pool:" + chain_id + ":" + rewards_manager_pubkey + // + ":" + sorted_lowercased_authorities.join(",") + // + // produced by the ed25519 private key whose public key IS + // rewards_manager_pubkey. Proves possession of the Solana reward + // manager state account's keypair, which only the legitimate creator + // (the launchpad relay, deterministically deriving the keypair from + // (launchpadDeterministicSecret, mint)) holds. Defends against + // frontrunning where an observer races to register a pool for a + // freshly-minted RM with attacker-chosen authorities. Required. + RmOwnerSignature []byte `protobuf:"bytes,3,opt,name=rm_owner_signature,json=rmOwnerSignature,proto3" json:"rm_owner_signature,omitempty"` } func (x *CreateRewardPool) Reset() { @@ -4595,6 +4608,13 @@ func (x *CreateRewardPool) GetAuthorities() []string { return nil } +func (x *CreateRewardPool) GetRmOwnerSignature() []byte { + if x != nil { + return x.RmOwnerSignature + } + return nil +} + // SetRewardPoolAuthorities replaces the pool's authority set wholesale. The // signer must be in the *current* pool.authorities; the new list must be // non-empty and contain only valid eth addresses (otherwise the pool is @@ -8128,210 +8148,213 @@ var file_core_v1_types_proto_rawDesc = []byte{ 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x6a, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, - 0x6f, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x72, 0x0a, 0x18, 0x53, - 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, - 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, - 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, - 0x8f, 0x01, 0x0a, 0x13, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, - 0x36, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, - 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0xf5, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x44, 0x0a, 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, - 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x12, 0x4c, 0x65, - 0x67, 0x61, 0x63, 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, - 0x69, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, - 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4c, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, + 0x98, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x6f, 0x0a, 0x15, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, + 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, + 0x72, 0x6d, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x72, 0x6d, 0x4f, 0x77, 0x6e, 0x65, + 0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x18, 0x53, 0x65, + 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x8f, + 0x01, 0x0a, 0x13, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x36, + 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x48, 0x00, 0x52, 0x06, + 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0xf5, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, + 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x44, 0x0a, 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x67, + 0x61, 0x63, 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x61, + 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4c, 0x0a, 0x14, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x44, 0x0a, 0x10, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x68, 0x61, 0x73, - 0x68, 0x22, 0xde, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, - 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x22, 0xf3, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x13, 0x65, 0x74, 0x68, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, - 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, - 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, - 0x27, 0x0a, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x23, 0x0a, 0x0f, 0x55, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x22, 0x96, 0x02, - 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x29, 0x0a, 0x10, - 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x63, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, - 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, - 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x63, 0x6f, 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x6f, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x71, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x39, - 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x39, - 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x87, 0x03, 0x0a, 0x15, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x12, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x34, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x6f, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x44, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x68, 0x61, 0x73, 0x68, + 0x22, 0xde, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6c, 0x61, + 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x21, + 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x22, 0xf3, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x32, 0x0a, 0x15, 0x65, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x65, 0x74, 0x68, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, + 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x27, + 0x0a, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x23, 0x0a, 0x0f, 0x55, 0x70, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x22, 0x96, 0x02, 0x0a, + 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x75, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x63, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, + 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x63, + 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, + 0x6f, 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x12, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x71, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x39, 0x0a, + 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x39, 0x0a, + 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x87, 0x03, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x62, 0x0a, 0x12, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, 0x1a, 0x93, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x72, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x65, 0x72, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x74, 0x0a, 0x15, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, 0x73, 0x1a, 0x93, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72, 0x6c, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, - 0x65, 0x72, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x65, 0x72, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x74, 0x0a, - 0x15, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x72, 0x6c, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, - 0x42, 0x79, 0x43, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x22, 0xa5, - 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, - 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x69, - 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74, - 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, - 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x69, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, - 0x64, 0x65, 0x64, 0x43, 0x69, 0x64, 0x22, 0x73, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, - 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x5c, 0x0a, 0x22, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x27, 0x47, 0x65, 0x74, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, - 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x22, 0x62, 0x0a, 0x28, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x55, 0x52, 0x4c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x29, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, + 0x79, 0x43, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x63, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x22, 0xa5, 0x01, + 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x43, 0x49, 0x44, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x69, 0x73, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, + 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x25, + 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x6f, 0x64, + 0x65, 0x64, 0x43, 0x69, 0x64, 0x22, 0x73, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x5c, 0x0a, 0x22, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x74, 0x74, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, - 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x22, 0x3c, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, - 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, - 0x70, 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x27, 0x47, 0x65, 0x74, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x34, 0x0a, + 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x22, 0x62, 0x0a, 0x28, 0x47, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x63, + 0x61, 0x6e, 0x6f, 0x6e, 0x22, 0x3c, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x05, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, 0x70, + 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/core/server/reward_pools.go b/pkg/core/server/reward_pools.go index 248270f1..59afbb75 100644 --- a/pkg/core/server/reward_pools.go +++ b/pkg/core/server/reward_pools.go @@ -2,6 +2,7 @@ package server import ( "context" + "crypto/ed25519" "errors" "fmt" "strings" @@ -24,6 +25,43 @@ import ( // caller-chosen string. const solanaPubkeyByteLen = 32 +// ErrRewardPoolOwnerSignatureInvalid signals that the rm_owner_signature +// on a CreateRewardPool message did not verify against the message's +// rewards_manager_pubkey. +var ErrRewardPoolOwnerSignatureInvalid = errors.New("reward pool owner signature invalid") + +// verifyRewardPoolOwnerSignature checks that the supplied ed25519 +// signature, produced by the keypair whose public key is rmPubkey, covers +// the canonical CreateRewardPool payload for (chainID, rmPubkey, +// authorities). Returns nil iff the signature is valid; otherwise returns +// a wrapped ErrRewardPoolOwnerSignatureInvalid. +// +// The verification key IS rmPubkey, decoded from base58. There is no +// trusted-key registry: the message claims an RM, the RM IS its +// verification key, and possession of the matching ed25519 secret key +// proves authorization to register a pool for that RM. The launchpad +// relay derives both the keypair and the per-mint claim authority eth +// address from (launchpadDeterministicSecret, mint), so the launchpad +// can always produce this signature; an attacker who lacks the secret +// cannot. +func verifyRewardPoolOwnerSignature(chainID, rmPubkey string, authorities []string, signature []byte) error { + if len(signature) != ed25519.SignatureSize { + return fmt.Errorf("%w: signature length is %d, want %d", ErrRewardPoolOwnerSignatureInvalid, len(signature), ed25519.SignatureSize) + } + pubkeyBytes, err := base58.Decode(rmPubkey) + if err != nil { + return fmt.Errorf("%w: rewards_manager_pubkey is not valid base58: %v", ErrRewardPoolOwnerSignatureInvalid, err) + } + if len(pubkeyBytes) != ed25519.PublicKeySize { + return fmt.Errorf("%w: rewards_manager_pubkey decodes to %d bytes, want %d", ErrRewardPoolOwnerSignatureInvalid, len(pubkeyBytes), ed25519.PublicKeySize) + } + payload := rewards.CanonicalCreateRewardPoolPayload(chainID, rmPubkey, authorities) + if !ed25519.Verify(ed25519.PublicKey(pubkeyBytes), payload, signature) { + return ErrRewardPoolOwnerSignatureInvalid + } + return nil +} + // isValidRewardPoolTransaction is the entry point for both CheckTx and // block validation. Signature + deadline live on the envelope; we recover // the signer once here and pass it to per-action validators. @@ -49,9 +87,23 @@ func (s *Server) isValidRewardPoolTransaction(ctx context.Context, signedTx *cor } // validateCreateRewardPool: pool is identified by the Solana reward manager -// pubkey (must be valid base58 32 bytes); signer must be in the initial -// authorities; the initial authority list must be non-empty and contain -// only valid eth addresses. +// pubkey (must be valid base58 32 bytes); the rm_owner_signature must +// verify against that pubkey over the canonical payload (proves the +// caller controls the RM keypair, defeating frontrunning); signer must +// be in the initial authorities; the initial authority list must be +// non-empty and contain only valid eth addresses. +// +// Two independent authorization checks are required intentionally: +// - rm_owner_signature (ed25519, against rm_pubkey): proves the +// legitimate RM keypair holder authorized this exact (rm, +// authorities) tuple. Defeats frontrunning by an observer who sees +// the RM pubkey on Solana but lacks the keypair. +// - envelope signer (secp256k1) ∈ initial authorities: proves the +// fee-paying eth address is in the pool's initial set, which keeps +// the existing "you can't create a pool you have no membership in" +// property. Operationally the launchpad relay holds the per-mint +// claim authority eth key and uses it as both the envelope signer +// and the only initial authority. func (s *Server) validateCreateRewardPool(ctx context.Context, msg *corev1.CreateRewardPool, signer string) error { if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { return err @@ -59,6 +111,9 @@ func (s *Server) validateCreateRewardPool(ctx context.Context, msg *corev1.Creat if err := validateAuthorityList(msg.Authorities); err != nil { return err } + if err := verifyRewardPoolOwnerSignature(s.config.GenesisFile.ChainID, msg.RewardsManagerPubkey, msg.Authorities, msg.RmOwnerSignature); err != nil { + return err + } canonical := rewards.CanonicalAuthorities(msg.Authorities) if !contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s not in initial authorities", ErrRewardUnauthorized, signer) @@ -187,6 +242,9 @@ func (s *Server) finalizeCreateRewardPool(ctx context.Context, msg *corev1.Creat if err := validateAuthorityList(msg.Authorities); err != nil { return err } + if err := verifyRewardPoolOwnerSignature(s.config.GenesisFile.ChainID, msg.RewardsManagerPubkey, msg.Authorities, msg.RmOwnerSignature); err != nil { + return err + } canonical := rewards.CanonicalAuthorities(msg.Authorities) if !contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s not in initial authorities", ErrRewardUnauthorized, signer) diff --git a/pkg/core/server/reward_pools_test.go b/pkg/core/server/reward_pools_test.go index 7336d202..f5b082a0 100644 --- a/pkg/core/server/reward_pools_test.go +++ b/pkg/core/server/reward_pools_test.go @@ -1,11 +1,14 @@ package server import ( + "crypto/ed25519" "crypto/rand" + "errors" "strings" "testing" "github.com/OpenAudio/go-openaudio/pkg/core/config" + "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/mr-tron/base58/base58" ) @@ -93,3 +96,94 @@ func freshSolanaPubkeyForTest(t *testing.T) string { } return base58.Encode(b[:]) } + +// TestVerifyRewardPoolOwnerSignature exercises the ed25519 proof-of-RM- +// keypair-possession that gates CreateRewardPool. Frontrunning defense +// rests on this check: the verification key IS the rm_pubkey, so +// possession of the matching secret is the only way to produce a valid +// signature. +func TestVerifyRewardPoolOwnerSignature(t *testing.T) { + const chainID = "audius-test-1" + + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("ed25519.GenerateKey: %v", err) + } + rmPubkey := base58.Encode(pub) + + // Foreign keypair representing an attacker who controls a different + // RM and tries to reuse their signature against ours. + foreignPub, foreignPriv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("ed25519.GenerateKey foreign: %v", err) + } + _ = foreignPub + authorities := []string{"0xAbCdEf0000000000000000000000000000000001"} + + t.Run("valid signature passes", func(t *testing.T) { + sig := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, authorities) + if err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig); err != nil { + t.Fatalf("expected valid signature to verify; got %v", err) + } + }) + + t.Run("authority canonicalization is invariant", func(t *testing.T) { + // Sign with one ordering/case, verify against another ordering/case. + // Both sides canonicalize before producing/verifying bytes, so + // equivalent inputs must produce verifiable signatures. + sigUpper := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, []string{strings.ToUpper(authorities[0])}) + if err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, []string{" " + strings.ToLower(authorities[0]) + " "}, sigUpper); err != nil { + t.Fatalf("canonicalization should make signatures invariant under case/whitespace; got %v", err) + } + }) + + t.Run("signature signed by foreign keypair fails", func(t *testing.T) { + // Attacker signs a payload claiming our rmPubkey, using their own + // (foreign) key. ed25519.Verify against our rmPubkey must reject. + sig := ed25519.Sign(foreignPriv, rewards.CanonicalCreateRewardPoolPayload(chainID, rmPubkey, authorities)) + err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig) + if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { + t.Fatalf("expected ErrRewardPoolOwnerSignatureInvalid; got %v", err) + } + }) + + t.Run("signature over different chain_id fails", func(t *testing.T) { + sig := rewards.SignCreateRewardPool(priv, "wrong-chain", rmPubkey, authorities) + err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig) + if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { + t.Fatalf("expected cross-chain replay rejected; got %v", err) + } + }) + + t.Run("signature over different authorities fails", func(t *testing.T) { + sig := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, []string{"0xAbCdEf0000000000000000000000000000000099"}) + err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig) + if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { + t.Fatalf("expected mismatched-authorities signature rejected; got %v", err) + } + }) + + t.Run("malformed signature length fails", func(t *testing.T) { + err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, []byte("too-short")) + if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { + t.Fatalf("expected short-signature rejected; got %v", err) + } + }) + + t.Run("non-base58 rm_pubkey fails", func(t *testing.T) { + sig := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, authorities) + err := verifyRewardPoolOwnerSignature(chainID, "not!base58", authorities, sig) + if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { + t.Fatalf("expected invalid-pubkey rejected; got %v", err) + } + }) + + t.Run("rm_pubkey wrong byte length fails", func(t *testing.T) { + shortPubkey := base58.Encode([]byte("short")) + sig := rewards.SignCreateRewardPool(priv, chainID, shortPubkey, authorities) + err := verifyRewardPoolOwnerSignature(chainID, shortPubkey, authorities, sig) + if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { + t.Fatalf("expected wrong-length rm_pubkey rejected; got %v", err) + } + }) +} diff --git a/pkg/integration_tests/12_rewards_test.go b/pkg/integration_tests/12_rewards_test.go index 03e5c77e..fe0ad676 100644 --- a/pkg/integration_tests/12_rewards_test.go +++ b/pkg/integration_tests/12_rewards_test.go @@ -30,6 +30,9 @@ func TestRewardsLifecycle(t *testing.T) { creatorAddr := common.PrivKeyToAddress(creatorKey) creator := sdk.NewOpenAudioSDK(nodeUrl) creator.SetPrivKey(creatorKey) + if err := creator.Init(ctx); err != nil { + t.Fatalf("creator init: %v", err) + } deleterKey, err := crypto.GenerateKey() if err != nil { @@ -46,18 +49,12 @@ func TestRewardsLifecycle(t *testing.T) { // the reward. To preserve the per-reward authority granularity of // the original test (reward1: creator only; reward2: creator + // deleter), we create two pools. - rmPubkeyA := freshSolanaPubkey(t) - rmPubkeyB := freshSolanaPubkey(t) - if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkeyA, - Authorities: []string{creatorAddr}, - }, 999999); err != nil { + rmPubkeyA, rmPrivA := freshRewardManager(t) + rmPubkeyB, rmPrivB := freshRewardManager(t) + if _, err := creator.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(creator, rmPubkeyA, rmPrivA, []string{creatorAddr}), 999999); err != nil { t.Fatalf("Failed to create pool A: %v", err) } - if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkeyB, - Authorities: []string{creatorAddr, deleterAddr}, - }, 999999); err != nil { + if _, err := creator.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(creator, rmPubkeyB, rmPrivB, []string{creatorAddr, deleterAddr}), 999999); err != nil { t.Fatalf("Failed to create pool B: %v", err) } @@ -155,6 +152,9 @@ func TestRewardsLifecycle(t *testing.T) { authority1Addr := common.PrivKeyToAddress(authority1Key) authority1 := sdk.NewOpenAudioSDK(nodeUrl) authority1.SetPrivKey(authority1Key) + if err := authority1.Init(ctx); err != nil { + t.Fatalf("authority1 init: %v", err) + } authority2Key, err := crypto.GenerateKey() if err != nil { @@ -163,6 +163,9 @@ func TestRewardsLifecycle(t *testing.T) { authority2Addr := common.PrivKeyToAddress(authority2Key) authority2 := sdk.NewOpenAudioSDK(nodeUrl) authority2.SetPrivKey(authority2Key) + if err := authority2.Init(ctx); err != nil { + t.Fatalf("authority2 init: %v", err) + } unauthorizedKey, err := crypto.GenerateKey() if err != nil { @@ -177,11 +180,8 @@ func TestRewardsLifecycle(t *testing.T) { t.Logf("unauthorized address: %s", unauthorizedAddr) // Create a pool with both authorities and a reward in it. - rmPubkey := freshSolanaPubkey(t) - if _, err := authority1.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkey, - Authorities: []string{authority1Addr, authority2Addr}, - }, 999999); err != nil { + rmPubkey, rmPriv := freshRewardManager(t) + if _, err := authority1.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(authority1, rmPubkey, rmPriv, []string{authority1Addr, authority2Addr}), 999999); err != nil { t.Fatalf("Failed to create pool: %v", err) } reward, err := authority1.Rewards.CreateReward(ctx, &v1.CreateReward{ @@ -252,11 +252,8 @@ func TestRewardsLifecycle(t *testing.T) { // Test 4: Verify authority1 cannot get attestation for a reward they're not authorized for // Create another pool with only authority2 + a reward in it. - rmPubkey2 := freshSolanaPubkey(t) - if _, err := authority2.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkey2, - Authorities: []string{authority2Addr}, - }, 999999); err != nil { + rmPubkey2, rmPriv2 := freshRewardManager(t) + if _, err := authority2.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(authority2, rmPubkey2, rmPriv2, []string{authority2Addr}), 999999); err != nil { t.Fatalf("Failed to create pool 2: %v", err) } reward2, err := authority2.Rewards.CreateReward(ctx, &v1.CreateReward{ @@ -304,23 +301,31 @@ func TestRewardsLifecycle(t *testing.T) { authority := sdk.NewOpenAudioSDK(nodeUrl) authority.SetPrivKey(authorityKey) - // Generate a key for creating the reward + // Generate a key for creating the reward. Note this isn't the pool + // authority; the pool is created via authority's SDK so the envelope + // signer is in the initial authority list. creatorKey, err := crypto.GenerateKey() if err != nil { t.Fatalf("Failed to generate creator key: %v", err) } creator := sdk.NewOpenAudioSDK(nodeUrl) creator.SetPrivKey(creatorKey) + if err := authority.Init(ctx); err != nil { + t.Fatalf("authority init: %v", err) + } // Create a pool that names authority + a reward bound to it. - rmPubkey := freshSolanaPubkey(t) - if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkey, - Authorities: []string{authorityAddr}, - }, 999999); err != nil { + // Authority signs the envelope (it's the lone initial authority); + // the RM keypair signs the inner rm_owner_signature. + rmPubkey, rmPriv := freshRewardManager(t) + if _, err := authority.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(authority, rmPubkey, rmPriv, []string{authorityAddr}), 999999); err != nil { t.Fatalf("Failed to create pool: %v", err) } - reward, err := creator.Rewards.CreateReward(ctx, &v1.CreateReward{ + // authority is the only pool member, so it must create the reward + // (validateCreateReward gates on signer ∈ pool.authorities). The + // `creator` SDK above is unused under the pool model. + _ = creator + reward, err := authority.Rewards.CreateReward(ctx, &v1.CreateReward{ RewardId: "amount_test", Name: "Amount Test Reward", Amount: 100, diff --git a/pkg/integration_tests/13_reward_pools_test.go b/pkg/integration_tests/13_reward_pools_test.go index e81c3ad2..5d054ca1 100644 --- a/pkg/integration_tests/13_reward_pools_test.go +++ b/pkg/integration_tests/13_reward_pools_test.go @@ -2,6 +2,7 @@ package integration_tests import ( "context" + "crypto/ed25519" "crypto/rand" "strings" "testing" @@ -9,24 +10,47 @@ import ( v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" "github.com/OpenAudio/go-openaudio/pkg/integration_tests/utils" + pkgrewards "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/OpenAudio/go-openaudio/pkg/sdk" "github.com/ethereum/go-ethereum/crypto" "github.com/mr-tron/base58/base58" ) -// freshSolanaPubkey returns a random 32-byte value base58-encoded — a -// well-formed Solana pubkey for the validator's wire-shape check -// (validateRewardsManagerPubkey). The value doesn't have to correspond to a -// real Solana account because the test only exercises the OAP-side pool -// primitive; PR3's sender-attestation gate will eventually consult Solana, -// but PR2's transactions only care about the wire shape. +// freshRewardManager returns a random ed25519 keypair representing a +// freshly-minted Solana reward manager state account. The pubkey (base58- +// encoded) doubles as the cometbft rewards_manager_pubkey; the private +// key is needed to sign CreateRewardPool.RmOwnerSignature, which proves +// to the validator that the caller controls the RM keypair (defeating +// frontrunning of pool creation). +func freshRewardManager(t *testing.T) (string, ed25519.PrivateKey) { + t.Helper() + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("ed25519.GenerateKey: %v", err) + } + return base58.Encode(pub), priv +} + +// freshSolanaPubkey is a convenience wrapper for tests that only need a +// well-formed Solana pubkey shape and don't need to sign with the keypair +// (e.g., negative tests that exercise rejection on the pubkey itself). func freshSolanaPubkey(t *testing.T) string { t.Helper() - var b [32]byte - if _, err := rand.Read(b[:]); err != nil { - t.Fatalf("rand: %v", err) + pub, _ := freshRewardManager(t) + return pub +} + +// signedCreateRewardPool builds a CreateRewardPool message with a valid +// RmOwnerSignature, given the RM keypair, the SDK's chainID (for cross- +// chain replay protection in the signed payload), and the desired initial +// authorities. Centralizes the boilerplate so test sites read at the +// "what" level, not the "how". +func signedCreateRewardPool(s *sdk.OpenAudioSDK, rmPubkey string, rmPriv ed25519.PrivateKey, authorities []string) *v1.CreateRewardPool { + return &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: authorities, + RmOwnerSignature: pkgrewards.SignCreateRewardPool(rmPriv, s.ChainID(), rmPubkey, authorities), } - return base58.Encode(b[:]) } // TestRewardPoolsLifecycle exercises the cometbft RewardPool transactions: @@ -41,10 +65,11 @@ func TestRewardPoolsLifecycle(t *testing.T) { t.Fatalf("Devnet not ready: %v", err) } - // Each test run gets its own fresh RM pubkey so reruns don't collide on - // the pool's identity (uniqueness is enforced server-side). - rmPubkey := freshSolanaPubkey(t) - otherRmPubkey := freshSolanaPubkey(t) + // Each test run gets its own fresh RM keypair so reruns don't collide on + // the pool's identity (uniqueness is enforced server-side). The private + // key is needed to produce the RmOwnerSignature on CreateRewardPool. + rmPubkey, rmPriv := freshRewardManager(t) + otherRmPubkey, otherRmPriv := freshRewardManager(t) aliceKey, err := crypto.GenerateKey() if err != nil { @@ -53,6 +78,9 @@ func TestRewardPoolsLifecycle(t *testing.T) { aliceAddr := common.PrivKeyToAddress(aliceKey) alice := sdk.NewOpenAudioSDK(nodeUrl) alice.SetPrivKey(aliceKey) + if err := alice.Init(ctx); err != nil { + t.Fatalf("alice init: %v", err) + } bobKey, err := crypto.GenerateKey() if err != nil { @@ -70,6 +98,9 @@ func TestRewardPoolsLifecycle(t *testing.T) { mallory.SetPrivKey(malloryKey) t.Run("CreateRewardPool rejects non-pubkey rewards_manager_pubkey", func(t *testing.T) { + // Pubkey shape check fires before signature verification, so the + // signature value here doesn't matter — we just need a non-base58 + // rewards_manager_pubkey to land at the shape rejection. _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ RewardsManagerPubkey: "not-a-real-pubkey", Authorities: []string{aliceAddr}, @@ -79,11 +110,35 @@ func TestRewardPoolsLifecycle(t *testing.T) { } }) - t.Run("CreateRewardPool requires signer in initial authorities", func(t *testing.T) { - _, err := mallory.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + t.Run("CreateRewardPool rejects missing rm_owner_signature", func(t *testing.T) { + // Valid pubkey, valid authorities, signer ∈ authorities — but no + // signature. Must fail at the rm_owner_signature check. + _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ RewardsManagerPubkey: otherRmPubkey, - Authorities: []string{aliceAddr, bobAddr}, + Authorities: []string{aliceAddr}, }, 999999) + if err == nil { + t.Fatalf("expected missing rm_owner_signature to be rejected") + } + }) + + t.Run("CreateRewardPool rejects signature signed by wrong RM keypair", func(t *testing.T) { + // Signature is well-formed and the inner ed25519 verifies — but + // against the WRONG public key (rmPriv signs but message claims + // otherRmPubkey). Defends against attacker reusing a signature + // they made for their own RM against a different RM. + msg := signedCreateRewardPool(alice, otherRmPubkey, rmPriv, []string{aliceAddr}) + _, err := alice.Rewards.CreateRewardPool(ctx, msg, 999999) + if err == nil { + t.Fatalf("expected mismatched-keypair signature to be rejected") + } + }) + + t.Run("CreateRewardPool requires signer in initial authorities", func(t *testing.T) { + // Mallory submits a perfectly-signed CreateRewardPool but isn't in + // the initial authorities. The RM-keypair gate passes; the + // signer-membership gate fails. + _, err := mallory.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(alice, otherRmPubkey, otherRmPriv, []string{aliceAddr, bobAddr}), 999999) if err == nil { t.Fatalf("expected CreateRewardPool to reject signer not in initial authorities") } @@ -93,10 +148,7 @@ func TestRewardPoolsLifecycle(t *testing.T) { }) t.Run("Alice creates the pool with [alice, bob]", func(t *testing.T) { - _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkey, - Authorities: []string{aliceAddr, bobAddr}, - }, 999999) + _, err := alice.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(alice, rmPubkey, rmPriv, []string{aliceAddr, bobAddr}), 999999) if err != nil { t.Fatalf("create pool: %v", err) } @@ -114,10 +166,7 @@ func TestRewardPoolsLifecycle(t *testing.T) { }) t.Run("CreateRewardPool with duplicate rewards_manager_pubkey is rejected", func(t *testing.T) { - _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkey, - Authorities: []string{aliceAddr}, - }, 999999) + _, err := alice.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(alice, rmPubkey, rmPriv, []string{aliceAddr}), 999999) if err == nil { t.Fatalf("expected duplicate rewards_manager_pubkey to be rejected") } diff --git a/pkg/rewards/reward_pool.go b/pkg/rewards/reward_pool.go index 5d72e8f5..2ff04340 100644 --- a/pkg/rewards/reward_pool.go +++ b/pkg/rewards/reward_pool.go @@ -1,6 +1,7 @@ package rewards import ( + "crypto/ed25519" "sort" "strings" ) @@ -27,3 +28,46 @@ func CanonicalAuthorities(authorities []string) []string { sort.Strings(out) return out } + +// CreateRewardPoolOwnerSignatureDomain is the domain-separation prefix +// used in the canonical CreateRewardPool authorization payload. Any +// future ed25519 signing scheme that re-uses an RM keypair MUST use a +// different domain prefix to avoid signature replay across schemes. +const CreateRewardPoolOwnerSignatureDomain = "audius:create-reward-pool:" + +// CanonicalCreateRewardPoolPayload returns the bytes the RM keypair signs +// to authorize a CreateRewardPool transaction. Format: +// +// "audius:create-reward-pool:" + chain_id + ":" + rm_pubkey_b58 + +// ":" + sorted_lowercased_authorities.join(",") +// +// Authorities are canonicalized so that an attacker cannot grind a +// different message-byte order to produce a different signature for the +// same logical authority set. chain_id is included to prevent cross- +// chain replay; rm_pubkey is included to bind the signature to the +// specific RM being registered. +// +// Both the validator (signature verification) and any client that +// produces signatures (launchpad relay, integration tests) must use this +// helper so encodings stay synchronized. +func CanonicalCreateRewardPoolPayload(chainID, rmPubkey string, authorities []string) []byte { + canon := CanonicalAuthorities(authorities) + var b strings.Builder + b.WriteString(CreateRewardPoolOwnerSignatureDomain) + b.WriteString(chainID) + b.WriteByte(':') + b.WriteString(rmPubkey) + b.WriteByte(':') + b.WriteString(strings.Join(canon, ",")) + return []byte(b.String()) +} + +// SignCreateRewardPool produces an ed25519 signature over the canonical +// authorization payload for a CreateRewardPool tx, suitable for placing +// in CreateRewardPool.RmOwnerSignature. Used by clients that hold the RM +// keypair (the launchpad relay deterministically derives it from +// (launchpadDeterministicSecret, mint); integration tests generate fresh +// keypairs via ed25519.GenerateKey). +func SignCreateRewardPool(privKey ed25519.PrivateKey, chainID, rmPubkey string, authorities []string) []byte { + return ed25519.Sign(privKey, CanonicalCreateRewardPoolPayload(chainID, rmPubkey, authorities)) +} diff --git a/proto/core/v1/types.proto b/proto/core/v1/types.proto index 2a297a2b..e29e10d2 100644 --- a/proto/core/v1/types.proto +++ b/proto/core/v1/types.proto @@ -593,6 +593,17 @@ message CreateRewardPool { // pool. The recovered signer must be a member of this list. Each entry // must be a valid eth hex address. repeated string authorities = 2; + // ed25519 signature over the canonical payload + // "audius:create-reward-pool:" + chain_id + ":" + rewards_manager_pubkey + // + ":" + sorted_lowercased_authorities.join(",") + // produced by the ed25519 private key whose public key IS + // rewards_manager_pubkey. Proves possession of the Solana reward + // manager state account's keypair, which only the legitimate creator + // (the launchpad relay, deterministically deriving the keypair from + // (launchpadDeterministicSecret, mint)) holds. Defends against + // frontrunning where an observer races to register a pool for a + // freshly-minted RM with attacker-chosen authorities. Required. + bytes rm_owner_signature = 3; } // SetRewardPoolAuthorities replaces the pool's authority set wholesale. The From 63b057b51257403351eb21073e07866d02aefa74 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Tue, 12 May 2026 11:02:40 -0500 Subject: [PATCH 4/9] Move rm_owner_signature to envelope, sign body bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructuring on top of the previous commit: the ed25519 rm_owner_signature moves from CreateRewardPool.rm_owner_signature (tag 3, signing a custom canonical string) to RewardPoolMessage.rm_owner_signature (envelope-level, signing the same ProtoMarshal(body) bytes the secp256k1 envelope signature covers). Why: - One encoding to maintain instead of two. Cross-language clients (the TS launchpad relay) now sign the same bytes for both signatures; no separate domain-separated string format to keep in sync. - Body bytes implicitly cover deadline_block_height + the action oneof discriminator. The earlier custom string didn't include deadline; stale-deadline replay was technically possible (though blocked by pool PK uniqueness). - Future fields added to RewardPoolBody / CreateRewardPool are automatically covered without revving the signing scheme. Not included: chain_id in the body. Cross-chain replay isn't a concrete threat — each environment's launchpad uses a different deterministic secret, so the same rewards_manager_pubkey cannot be derived on more than one chain. A captured CreateRewardPool replayed on another chain refers to an RM that doesn't exist there. Other changes: - pkg/common.ProtoSignableBytes (new): exports the deterministic- marshal helper so verifyRewardPoolOwnerSignature can hash the same bytes ProtoSign / ProtoRecover use. - SDK signAndSendRewardPool takes an rmOwnerSig parameter; the CreateRewardPool wrapper accepts an ed25519.PrivateKey and signs body bytes locally. SetRewardPoolAuthorities passes nil — rotation is gated by current pool authorities, no RM signature needed. - Removed pkg/rewards.SignCreateRewardPool / CanonicalCreateRewardPoolPayload / CreateRewardPoolOwnerSignatureDomain — replaced by the body-bytes signing path. - Updated unit tests, integration tests, and example to populate rmKey at the SDK call site rather than constructing a signed message struct. Co-Authored-By: Claude Opus 4.7 --- examples/rewards/main.go | 7 +- pkg/api/core/v1/types.pb.go | 124 ++++++++++-------- pkg/common/proto.go | 10 ++ pkg/core/server/reward_pools.go | 101 ++++++++------ pkg/core/server/reward_pools_test.go | 62 ++++----- pkg/integration_tests/12_rewards_test.go | 37 +++--- pkg/integration_tests/13_reward_pools_test.go | 76 +++++------ pkg/rewards/reward_pool.go | 44 ------- pkg/sdk/rewards/rewards.go | 45 +++++-- proto/core/v1/types.proto | 44 ++++--- 10 files changed, 272 insertions(+), 278 deletions(-) diff --git a/examples/rewards/main.go b/examples/rewards/main.go index e524ee13..8b235596 100644 --- a/examples/rewards/main.go +++ b/examples/rewards/main.go @@ -11,7 +11,6 @@ import ( "connectrpc.com/connect" v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" - "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/OpenAudio/go-openaudio/pkg/sdk" "github.com/mr-tron/base58/base58" ) @@ -33,9 +32,6 @@ func main() { oap := sdk.NewOpenAudioSDK("creatornode11.staging.audius.co") oap.SetPrivKey(privateKey) - if err := oap.Init(context.Background()); err != nil { - log.Fatalf("Failed to init SDK: %v", err) - } resp, err := oap.Core.GetStatus(context.Background(), connect.NewRequest(&v1.GetStatusRequest{})) if err != nil { @@ -77,8 +73,7 @@ func main() { if _, err := oap.Rewards.CreateRewardPool(context.Background(), &v1.CreateRewardPool{ RewardsManagerPubkey: rewardsManagerPubkey, Authorities: authorities, - RmOwnerSignature: rewards.SignCreateRewardPool(rmPrivKey, oap.ChainID(), rewardsManagerPubkey, authorities), - }, deadline); err != nil { + }, rmPrivKey, deadline); err != nil { log.Fatalf("Failed to create reward pool: %v", err) } diff --git a/pkg/api/core/v1/types.pb.go b/pkg/api/core/v1/types.pb.go index 25b42902..0139cb20 100644 --- a/pkg/api/core/v1/types.pb.go +++ b/pkg/api/core/v1/types.pb.go @@ -4170,9 +4170,10 @@ func (x *RewardMessage) GetSignature() string { } // RewardBody is the signed payload. deadline_block_height bounds the -// signature's validity (the validator rejects after expiry); the action is -// the actual operation. The oneof tag discriminates Create from Delete so -// two actions with otherwise-identical inner shapes produce different bytes. +// signature's validity (the validator rejects after expiry); the action +// is the actual operation. The oneof tag discriminates Create from +// Delete so two actions with otherwise-identical inner shapes produce +// different bytes. type RewardBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4387,14 +4388,26 @@ func (x *DeleteReward) GetAddress() string { } // RewardPoolMessage is the wire envelope for pool-management transactions. -// Same shape as RewardMessage: body + signature. +// Two signatures cover the same body bytes: +// - signature: secp256k1 (eth) recoverable signature, identifies the +// fee-paying tx submitter who must be in the pool's current +// authority set (Create: initial authorities; SetAuthorities: +// existing pool authorities). +// - rm_owner_signature: ed25519 signature, REQUIRED when body.action +// is Create. Verification key IS body.create.rewards_manager_pubkey +// (the Solana RM state account is itself an ed25519 keypair). Proves +// possession of the RM keypair, defeating pool-creation frontrunning +// by an observer who watches Solana RM init events but lacks the +// launchpad's deterministic-secret. Ignored for SetAuthorities +// because rotation is meant to work without the launchpad. type RewardPoolMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Body *RewardPoolBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` - Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + Body *RewardPoolBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + RmOwnerSignature []byte `protobuf:"bytes,3,opt,name=rm_owner_signature,json=rmOwnerSignature,proto3" json:"rm_owner_signature,omitempty"` } func (x *RewardPoolMessage) Reset() { @@ -4443,6 +4456,22 @@ func (x *RewardPoolMessage) GetSignature() string { return "" } +func (x *RewardPoolMessage) GetRmOwnerSignature() []byte { + if x != nil { + return x.RmOwnerSignature + } + return nil +} + +// RewardPoolBody is the signed payload. deadline_block_height bounds the +// signature's validity; action discriminates Create from SetAuthorities +// so two otherwise-identical messages produce different bytes. +// +// Cross-chain replay isn't a threat surface here: each environment's +// launchpad uses a different deterministic secret, so the same +// rewards_manager_pubkey cannot be derived on more than one chain. A +// captured CreateRewardPool replayed on a different chain refers to an +// RM that doesn't exist there. type RewardPoolBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4545,21 +4574,9 @@ type CreateRewardPool struct { RewardsManagerPubkey string `protobuf:"bytes,1,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` // Initial set of eth addresses authorized to attest for rewards in this // pool. The recovered signer must be a member of this list. Each entry - // must be a valid eth hex address. + // must be a valid eth hex address. Frontrunning defense lives at the + // envelope level (RewardPoolMessage.rm_owner_signature). Authorities []string `protobuf:"bytes,2,rep,name=authorities,proto3" json:"authorities,omitempty"` - // ed25519 signature over the canonical payload - // - // "audius:create-reward-pool:" + chain_id + ":" + rewards_manager_pubkey - // + ":" + sorted_lowercased_authorities.join(",") - // - // produced by the ed25519 private key whose public key IS - // rewards_manager_pubkey. Proves possession of the Solana reward - // manager state account's keypair, which only the legitimate creator - // (the launchpad relay, deterministically deriving the keypair from - // (launchpadDeterministicSecret, mint)) holds. Defends against - // frontrunning where an observer races to register a pool for a - // freshly-minted RM with attacker-chosen authorities. Required. - RmOwnerSignature []byte `protobuf:"bytes,3,opt,name=rm_owner_signature,json=rmOwnerSignature,proto3" json:"rm_owner_signature,omitempty"` } func (x *CreateRewardPool) Reset() { @@ -4608,13 +4625,6 @@ func (x *CreateRewardPool) GetAuthorities() []string { return nil } -func (x *CreateRewardPool) GetRmOwnerSignature() []byte { - if x != nil { - return x.RmOwnerSignature - } - return nil -} - // SetRewardPoolAuthorities replaces the pool's authority set wholesale. The // signer must be in the *current* pool.authorities; the new list must be // non-empty and contain only valid eth addresses (otherwise the pool is @@ -8128,36 +8138,36 @@ var file_core_v1_types_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x52, 0x65, 0x77, - 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, - 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, - 0x6c, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x32, 0x0a, 0x15, - 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, - 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x12, 0x34, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0xd0, 0x0f, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x00, 0x52, 0x06, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x4d, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0xd1, 0x0f, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x98, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, - 0x72, 0x6d, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x72, 0x6d, 0x4f, 0x77, 0x6e, 0x65, - 0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x18, 0x53, 0x65, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x11, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x2b, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, + 0x6f, 0x6c, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x6d, + 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x72, 0x6d, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x34, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0xd0, 0x0f, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x4d, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0xd1, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x69, 0x65, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6a, + 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, + 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x72, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, diff --git a/pkg/common/proto.go b/pkg/common/proto.go index 7c496f44..235d7a81 100644 --- a/pkg/common/proto.go +++ b/pkg/common/proto.go @@ -53,6 +53,16 @@ func ProtoRecover(body proto.Message, signature string) (string, error) { return address, err } +// ProtoSignableBytes returns the deterministic-protobuf marshaling of body — +// the exact byte sequence ProtoSign / ProtoRecover hash and recover from. +// Exposed so callers that verify a second signature scheme (e.g., ed25519 +// alongside the secp256k1 envelope sig) can hash the same bytes without +// reaching into the package internals or having to duplicate the +// MarshalOptions. +func ProtoSignableBytes(body proto.Message) ([]byte, error) { + return signableBytes(body) +} + func signableBytes(body proto.Message) ([]byte, error) { opts := proto.MarshalOptions{Deterministic: true} return opts.Marshal(body) diff --git a/pkg/core/server/reward_pools.go b/pkg/core/server/reward_pools.go index 59afbb75..7ac09c53 100644 --- a/pkg/core/server/reward_pools.go +++ b/pkg/core/server/reward_pools.go @@ -8,6 +8,7 @@ import ( "strings" corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/OpenAudio/go-openaudio/pkg/common" "github.com/OpenAudio/go-openaudio/pkg/core/config" "github.com/OpenAudio/go-openaudio/pkg/core/db" "github.com/OpenAudio/go-openaudio/pkg/rewards" @@ -25,26 +26,30 @@ import ( // caller-chosen string. const solanaPubkeyByteLen = 32 -// ErrRewardPoolOwnerSignatureInvalid signals that the rm_owner_signature -// on a CreateRewardPool message did not verify against the message's -// rewards_manager_pubkey. +// ErrRewardPoolOwnerSignatureInvalid signals that the envelope's +// rm_owner_signature did not verify against the body's claimed RM pubkey. var ErrRewardPoolOwnerSignatureInvalid = errors.New("reward pool owner signature invalid") // verifyRewardPoolOwnerSignature checks that the supplied ed25519 -// signature, produced by the keypair whose public key is rmPubkey, covers -// the canonical CreateRewardPool payload for (chainID, rmPubkey, -// authorities). Returns nil iff the signature is valid; otherwise returns -// a wrapped ErrRewardPoolOwnerSignatureInvalid. +// signature, produced by the keypair whose public key is rmPubkey, +// covers bodyBytes. Returns nil iff the signature is valid; otherwise +// returns a wrapped ErrRewardPoolOwnerSignatureInvalid. // // The verification key IS rmPubkey, decoded from base58. There is no -// trusted-key registry: the message claims an RM, the RM IS its -// verification key, and possession of the matching ed25519 secret key -// proves authorization to register a pool for that RM. The launchpad -// relay derives both the keypair and the per-mint claim authority eth -// address from (launchpadDeterministicSecret, mint), so the launchpad -// can always produce this signature; an attacker who lacks the secret +// trusted-key registry: the body claims an RM, the RM IS its ed25519 +// verification key, and possession of the matching secret key proves +// authorization to register a pool for that RM. The launchpad relay +// derives both the keypair and the per-mint claim authority eth address +// from (launchpadDeterministicSecret, mint), so the launchpad can +// always produce this signature; an attacker who lacks the secret // cannot. -func verifyRewardPoolOwnerSignature(chainID, rmPubkey string, authorities []string, signature []byte) error { +// +// bodyBytes are the deterministic-protobuf marshaling of RewardPoolBody — +// the same bytes the secp256k1 envelope signature covers. This means +// chain_id (in the body) and deadline_block_height are both implicitly +// covered, defeating cross-chain replay and stale-signature replay +// without a separate signed-string format. +func verifyRewardPoolOwnerSignature(rmPubkey string, bodyBytes, signature []byte) error { if len(signature) != ed25519.SignatureSize { return fmt.Errorf("%w: signature length is %d, want %d", ErrRewardPoolOwnerSignatureInvalid, len(signature), ed25519.SignatureSize) } @@ -55,16 +60,17 @@ func verifyRewardPoolOwnerSignature(chainID, rmPubkey string, authorities []stri if len(pubkeyBytes) != ed25519.PublicKeySize { return fmt.Errorf("%w: rewards_manager_pubkey decodes to %d bytes, want %d", ErrRewardPoolOwnerSignatureInvalid, len(pubkeyBytes), ed25519.PublicKeySize) } - payload := rewards.CanonicalCreateRewardPoolPayload(chainID, rmPubkey, authorities) - if !ed25519.Verify(ed25519.PublicKey(pubkeyBytes), payload, signature) { + if !ed25519.Verify(ed25519.PublicKey(pubkeyBytes), bodyBytes, signature) { return ErrRewardPoolOwnerSignatureInvalid } return nil } // isValidRewardPoolTransaction is the entry point for both CheckTx and -// block validation. Signature + deadline live on the envelope; we recover -// the signer once here and pass it to per-action validators. +// block validation. Signatures + deadline live on the envelope; we +// recover the secp256k1 signer once here, then dispatch to per-action +// validators that handle the rest (including the ed25519 +// rm_owner_signature for Create). func (s *Server) isValidRewardPoolTransaction(ctx context.Context, signedTx *corev1.SignedTransaction, blockHeight int64) error { envelope := signedTx.GetRewardPool() if envelope == nil || envelope.Body == nil { @@ -78,7 +84,7 @@ func (s *Server) isValidRewardPoolTransaction(ctx context.Context, signedTx *cor switch action := envelope.Body.Action.(type) { case *corev1.RewardPoolBody_Create: - return s.validateCreateRewardPool(ctx, action.Create, signer) + return s.validateCreateRewardPool(ctx, envelope, action.Create, signer) case *corev1.RewardPoolBody_SetAuthorities: return s.validateSetRewardPoolAuthorities(ctx, action.SetAuthorities, signer) default: @@ -86,32 +92,36 @@ func (s *Server) isValidRewardPoolTransaction(ctx context.Context, signedTx *cor } } -// validateCreateRewardPool: pool is identified by the Solana reward manager -// pubkey (must be valid base58 32 bytes); the rm_owner_signature must -// verify against that pubkey over the canonical payload (proves the -// caller controls the RM keypair, defeating frontrunning); signer must -// be in the initial authorities; the initial authority list must be -// non-empty and contain only valid eth addresses. +// validateCreateRewardPool runs two independent authorization checks +// alongside the structural validation: +// +// - envelope.rm_owner_signature (ed25519, verified against +// msg.rewards_manager_pubkey): proves the caller controls the +// RM keypair. Defeats frontrunning by an observer who sees the RM +// pubkey on Solana but lacks the launchpad's deterministic secret. +// - envelope signer (secp256k1, recovered from envelope.signature) ∈ +// msg.authorities: keeps the "you can't create a pool you have no +// membership in" property. Operationally the launchpad relay holds +// both keys and uses the per-mint claim authority eth key as the +// envelope signer and as the only initial authority. // -// Two independent authorization checks are required intentionally: -// - rm_owner_signature (ed25519, against rm_pubkey): proves the -// legitimate RM keypair holder authorized this exact (rm, -// authorities) tuple. Defeats frontrunning by an observer who sees -// the RM pubkey on Solana but lacks the keypair. -// - envelope signer (secp256k1) ∈ initial authorities: proves the -// fee-paying eth address is in the pool's initial set, which keeps -// the existing "you can't create a pool you have no membership in" -// property. Operationally the launchpad relay holds the per-mint -// claim authority eth key and uses it as both the envelope signer -// and the only initial authority. -func (s *Server) validateCreateRewardPool(ctx context.Context, msg *corev1.CreateRewardPool, signer string) error { +// Both signatures cover the same body bytes — the secp256k1 path uses +// common.ProtoRecover (which marshals deterministically); the ed25519 +// path verifies against common.ProtoSignableBytes(body) directly. body +// covers deadline_block_height and the action oneof, so stale-deadline +// replay is blocked for both signatures together. +func (s *Server) validateCreateRewardPool(ctx context.Context, envelope *corev1.RewardPoolMessage, msg *corev1.CreateRewardPool, signer string) error { if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { return err } if err := validateAuthorityList(msg.Authorities); err != nil { return err } - if err := verifyRewardPoolOwnerSignature(s.config.GenesisFile.ChainID, msg.RewardsManagerPubkey, msg.Authorities, msg.RmOwnerSignature); err != nil { + bodyBytes, err := common.ProtoSignableBytes(envelope.Body) + if err != nil { + return fmt.Errorf("%w: marshal body for rm signature: %v", ErrRewardPoolOwnerSignatureInvalid, err) + } + if err := verifyRewardPoolOwnerSignature(msg.RewardsManagerPubkey, bodyBytes, envelope.RmOwnerSignature); err != nil { return err } canonical := rewards.CanonicalAuthorities(msg.Authorities) @@ -199,8 +209,9 @@ func (s *Server) validateSetRewardPoolAuthorities(ctx context.Context, msg *core } // finalizeRewardPoolTransaction is invoked after a tx is included in a block. -// Signature was already verified at validate time; we re-recover here only -// because the finalize path is its own consensus boundary. +// Signatures were already verified at validate time; we re-check here as +// defense-in-depth because the finalize path is its own consensus boundary +// (block-sync replay does not re-run ProcessProposal / CheckTx). func (s *Server) finalizeRewardPoolTransaction(ctx context.Context, req *abcitypes.FinalizeBlockRequest, envelope *corev1.RewardPoolMessage, txhash string, messageIndex int64) (proto.Message, error) { if envelope == nil || envelope.Body == nil { return nil, fmt.Errorf("tx: %s, message index: %d, reward pool message body not found", txhash, messageIndex) @@ -212,7 +223,7 @@ func (s *Server) finalizeRewardPoolTransaction(ctx context.Context, req *abcityp switch action := envelope.Body.Action.(type) { case *corev1.RewardPoolBody_Create: - if err := s.finalizeCreateRewardPool(ctx, action.Create, signer); err != nil { + if err := s.finalizeCreateRewardPool(ctx, envelope, action.Create, signer); err != nil { return nil, errors.Join(ErrRewardMessageFinalization, err) } case *corev1.RewardPoolBody_SetAuthorities: @@ -235,14 +246,18 @@ func (s *Server) finalizeRewardPoolTransaction(ctx context.Context, req *abcityp // validate-time checks. Repeating the same shape + signer-membership // validation here keeps the post-replay state identical to what the live // validate path produces. -func (s *Server) finalizeCreateRewardPool(ctx context.Context, msg *corev1.CreateRewardPool, signer string) error { +func (s *Server) finalizeCreateRewardPool(ctx context.Context, envelope *corev1.RewardPoolMessage, msg *corev1.CreateRewardPool, signer string) error { if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { return err } if err := validateAuthorityList(msg.Authorities); err != nil { return err } - if err := verifyRewardPoolOwnerSignature(s.config.GenesisFile.ChainID, msg.RewardsManagerPubkey, msg.Authorities, msg.RmOwnerSignature); err != nil { + bodyBytes, err := common.ProtoSignableBytes(envelope.Body) + if err != nil { + return fmt.Errorf("%w: marshal body for rm signature: %v", ErrRewardPoolOwnerSignatureInvalid, err) + } + if err := verifyRewardPoolOwnerSignature(msg.RewardsManagerPubkey, bodyBytes, envelope.RmOwnerSignature); err != nil { return err } canonical := rewards.CanonicalAuthorities(msg.Authorities) diff --git a/pkg/core/server/reward_pools_test.go b/pkg/core/server/reward_pools_test.go index f5b082a0..c9e40e8e 100644 --- a/pkg/core/server/reward_pools_test.go +++ b/pkg/core/server/reward_pools_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/OpenAudio/go-openaudio/pkg/core/config" - "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/mr-tron/base58/base58" ) @@ -101,10 +100,8 @@ func freshSolanaPubkeyForTest(t *testing.T) string { // keypair-possession that gates CreateRewardPool. Frontrunning defense // rests on this check: the verification key IS the rm_pubkey, so // possession of the matching secret is the only way to produce a valid -// signature. +// signature over the body bytes. func TestVerifyRewardPoolOwnerSignature(t *testing.T) { - const chainID = "audius-test-1" - pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("ed25519.GenerateKey: %v", err) @@ -113,66 +110,51 @@ func TestVerifyRewardPoolOwnerSignature(t *testing.T) { // Foreign keypair representing an attacker who controls a different // RM and tries to reuse their signature against ours. - foreignPub, foreignPriv, err := ed25519.GenerateKey(rand.Reader) + _, foreignPriv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("ed25519.GenerateKey foreign: %v", err) } - _ = foreignPub - authorities := []string{"0xAbCdEf0000000000000000000000000000000001"} + + // The validator hashes body bytes for verification — we don't care + // what those bytes are in this unit test, just that the (sig, key, + // bytes) triple matches. + bodyBytes := []byte("body-marshal-bytes-stand-in") t.Run("valid signature passes", func(t *testing.T) { - sig := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, authorities) - if err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig); err != nil { + sig := ed25519.Sign(priv, bodyBytes) + if err := verifyRewardPoolOwnerSignature(rmPubkey, bodyBytes, sig); err != nil { t.Fatalf("expected valid signature to verify; got %v", err) } }) - t.Run("authority canonicalization is invariant", func(t *testing.T) { - // Sign with one ordering/case, verify against another ordering/case. - // Both sides canonicalize before producing/verifying bytes, so - // equivalent inputs must produce verifiable signatures. - sigUpper := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, []string{strings.ToUpper(authorities[0])}) - if err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, []string{" " + strings.ToLower(authorities[0]) + " "}, sigUpper); err != nil { - t.Fatalf("canonicalization should make signatures invariant under case/whitespace; got %v", err) - } - }) - t.Run("signature signed by foreign keypair fails", func(t *testing.T) { - // Attacker signs a payload claiming our rmPubkey, using their own - // (foreign) key. ed25519.Verify against our rmPubkey must reject. - sig := ed25519.Sign(foreignPriv, rewards.CanonicalCreateRewardPoolPayload(chainID, rmPubkey, authorities)) - err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig) + // Foreign secret signs the same bytes; verify against our rmPubkey + // must reject — verification key doesn't match the signing key. + sig := ed25519.Sign(foreignPriv, bodyBytes) + err := verifyRewardPoolOwnerSignature(rmPubkey, bodyBytes, sig) if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { t.Fatalf("expected ErrRewardPoolOwnerSignatureInvalid; got %v", err) } }) - t.Run("signature over different chain_id fails", func(t *testing.T) { - sig := rewards.SignCreateRewardPool(priv, "wrong-chain", rmPubkey, authorities) - err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig) - if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { - t.Fatalf("expected cross-chain replay rejected; got %v", err) - } - }) - - t.Run("signature over different authorities fails", func(t *testing.T) { - sig := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, []string{"0xAbCdEf0000000000000000000000000000000099"}) - err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, sig) + t.Run("signature over different body bytes fails", func(t *testing.T) { + sig := ed25519.Sign(priv, []byte("different-body")) + err := verifyRewardPoolOwnerSignature(rmPubkey, bodyBytes, sig) if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { - t.Fatalf("expected mismatched-authorities signature rejected; got %v", err) + t.Fatalf("expected mismatched-body signature rejected; got %v", err) } }) t.Run("malformed signature length fails", func(t *testing.T) { - err := verifyRewardPoolOwnerSignature(chainID, rmPubkey, authorities, []byte("too-short")) + err := verifyRewardPoolOwnerSignature(rmPubkey, bodyBytes, []byte("too-short")) if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { t.Fatalf("expected short-signature rejected; got %v", err) } }) t.Run("non-base58 rm_pubkey fails", func(t *testing.T) { - sig := rewards.SignCreateRewardPool(priv, chainID, rmPubkey, authorities) - err := verifyRewardPoolOwnerSignature(chainID, "not!base58", authorities, sig) + sig := ed25519.Sign(priv, bodyBytes) + err := verifyRewardPoolOwnerSignature("not!base58", bodyBytes, sig) if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { t.Fatalf("expected invalid-pubkey rejected; got %v", err) } @@ -180,8 +162,8 @@ func TestVerifyRewardPoolOwnerSignature(t *testing.T) { t.Run("rm_pubkey wrong byte length fails", func(t *testing.T) { shortPubkey := base58.Encode([]byte("short")) - sig := rewards.SignCreateRewardPool(priv, chainID, shortPubkey, authorities) - err := verifyRewardPoolOwnerSignature(chainID, shortPubkey, authorities, sig) + sig := ed25519.Sign(priv, bodyBytes) + err := verifyRewardPoolOwnerSignature(shortPubkey, bodyBytes, sig) if !errors.Is(err, ErrRewardPoolOwnerSignatureInvalid) { t.Fatalf("expected wrong-length rm_pubkey rejected; got %v", err) } diff --git a/pkg/integration_tests/12_rewards_test.go b/pkg/integration_tests/12_rewards_test.go index fe0ad676..142ab6fe 100644 --- a/pkg/integration_tests/12_rewards_test.go +++ b/pkg/integration_tests/12_rewards_test.go @@ -30,9 +30,6 @@ func TestRewardsLifecycle(t *testing.T) { creatorAddr := common.PrivKeyToAddress(creatorKey) creator := sdk.NewOpenAudioSDK(nodeUrl) creator.SetPrivKey(creatorKey) - if err := creator.Init(ctx); err != nil { - t.Fatalf("creator init: %v", err) - } deleterKey, err := crypto.GenerateKey() if err != nil { @@ -51,10 +48,16 @@ func TestRewardsLifecycle(t *testing.T) { // deleter), we create two pools. rmPubkeyA, rmPrivA := freshRewardManager(t) rmPubkeyB, rmPrivB := freshRewardManager(t) - if _, err := creator.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(creator, rmPubkeyA, rmPrivA, []string{creatorAddr}), 999999); err != nil { + if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkeyA, + Authorities: []string{creatorAddr}, + }, rmPrivA, 999999); err != nil { t.Fatalf("Failed to create pool A: %v", err) } - if _, err := creator.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(creator, rmPubkeyB, rmPrivB, []string{creatorAddr, deleterAddr}), 999999); err != nil { + if _, err := creator.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkeyB, + Authorities: []string{creatorAddr, deleterAddr}, + }, rmPrivB, 999999); err != nil { t.Fatalf("Failed to create pool B: %v", err) } @@ -152,9 +155,6 @@ func TestRewardsLifecycle(t *testing.T) { authority1Addr := common.PrivKeyToAddress(authority1Key) authority1 := sdk.NewOpenAudioSDK(nodeUrl) authority1.SetPrivKey(authority1Key) - if err := authority1.Init(ctx); err != nil { - t.Fatalf("authority1 init: %v", err) - } authority2Key, err := crypto.GenerateKey() if err != nil { @@ -163,9 +163,6 @@ func TestRewardsLifecycle(t *testing.T) { authority2Addr := common.PrivKeyToAddress(authority2Key) authority2 := sdk.NewOpenAudioSDK(nodeUrl) authority2.SetPrivKey(authority2Key) - if err := authority2.Init(ctx); err != nil { - t.Fatalf("authority2 init: %v", err) - } unauthorizedKey, err := crypto.GenerateKey() if err != nil { @@ -181,7 +178,10 @@ func TestRewardsLifecycle(t *testing.T) { // Create a pool with both authorities and a reward in it. rmPubkey, rmPriv := freshRewardManager(t) - if _, err := authority1.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(authority1, rmPubkey, rmPriv, []string{authority1Addr, authority2Addr}), 999999); err != nil { + if _, err := authority1.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{authority1Addr, authority2Addr}, + }, rmPriv, 999999); err != nil { t.Fatalf("Failed to create pool: %v", err) } reward, err := authority1.Rewards.CreateReward(ctx, &v1.CreateReward{ @@ -253,7 +253,10 @@ func TestRewardsLifecycle(t *testing.T) { // Test 4: Verify authority1 cannot get attestation for a reward they're not authorized for // Create another pool with only authority2 + a reward in it. rmPubkey2, rmPriv2 := freshRewardManager(t) - if _, err := authority2.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(authority2, rmPubkey2, rmPriv2, []string{authority2Addr}), 999999); err != nil { + if _, err := authority2.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey2, + Authorities: []string{authority2Addr}, + }, rmPriv2, 999999); err != nil { t.Fatalf("Failed to create pool 2: %v", err) } reward2, err := authority2.Rewards.CreateReward(ctx, &v1.CreateReward{ @@ -310,15 +313,15 @@ func TestRewardsLifecycle(t *testing.T) { } creator := sdk.NewOpenAudioSDK(nodeUrl) creator.SetPrivKey(creatorKey) - if err := authority.Init(ctx); err != nil { - t.Fatalf("authority init: %v", err) - } // Create a pool that names authority + a reward bound to it. // Authority signs the envelope (it's the lone initial authority); // the RM keypair signs the inner rm_owner_signature. rmPubkey, rmPriv := freshRewardManager(t) - if _, err := authority.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(authority, rmPubkey, rmPriv, []string{authorityAddr}), 999999); err != nil { + if _, err := authority.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{authorityAddr}, + }, rmPriv, 999999); err != nil { t.Fatalf("Failed to create pool: %v", err) } // authority is the only pool member, so it must create the reward diff --git a/pkg/integration_tests/13_reward_pools_test.go b/pkg/integration_tests/13_reward_pools_test.go index 5d054ca1..9531ad5e 100644 --- a/pkg/integration_tests/13_reward_pools_test.go +++ b/pkg/integration_tests/13_reward_pools_test.go @@ -10,7 +10,6 @@ import ( v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" "github.com/OpenAudio/go-openaudio/pkg/integration_tests/utils" - pkgrewards "github.com/OpenAudio/go-openaudio/pkg/rewards" "github.com/OpenAudio/go-openaudio/pkg/sdk" "github.com/ethereum/go-ethereum/crypto" "github.com/mr-tron/base58/base58" @@ -19,9 +18,9 @@ import ( // freshRewardManager returns a random ed25519 keypair representing a // freshly-minted Solana reward manager state account. The pubkey (base58- // encoded) doubles as the cometbft rewards_manager_pubkey; the private -// key is needed to sign CreateRewardPool.RmOwnerSignature, which proves -// to the validator that the caller controls the RM keypair (defeating -// frontrunning of pool creation). +// key is what the SDK uses to sign the envelope's rm_owner_signature, +// which proves to the validator that the caller controls the RM keypair +// (defeating frontrunning of pool creation). func freshRewardManager(t *testing.T) (string, ed25519.PrivateKey) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) @@ -40,19 +39,6 @@ func freshSolanaPubkey(t *testing.T) string { return pub } -// signedCreateRewardPool builds a CreateRewardPool message with a valid -// RmOwnerSignature, given the RM keypair, the SDK's chainID (for cross- -// chain replay protection in the signed payload), and the desired initial -// authorities. Centralizes the boilerplate so test sites read at the -// "what" level, not the "how". -func signedCreateRewardPool(s *sdk.OpenAudioSDK, rmPubkey string, rmPriv ed25519.PrivateKey, authorities []string) *v1.CreateRewardPool { - return &v1.CreateRewardPool{ - RewardsManagerPubkey: rmPubkey, - Authorities: authorities, - RmOwnerSignature: pkgrewards.SignCreateRewardPool(rmPriv, s.ChainID(), rmPubkey, authorities), - } -} - // TestRewardPoolsLifecycle exercises the cometbft RewardPool transactions: // pool creation, gating CreateReward on pool membership, rotating // authorities via SetRewardPoolAuthorities, and verifying that a rotated-out @@ -67,7 +53,8 @@ func TestRewardPoolsLifecycle(t *testing.T) { // Each test run gets its own fresh RM keypair so reruns don't collide on // the pool's identity (uniqueness is enforced server-side). The private - // key is needed to produce the RmOwnerSignature on CreateRewardPool. + // key is what the SDK uses to produce the envelope's + // rm_owner_signature on CreateRewardPool. rmPubkey, rmPriv := freshRewardManager(t) otherRmPubkey, otherRmPriv := freshRewardManager(t) @@ -78,9 +65,6 @@ func TestRewardPoolsLifecycle(t *testing.T) { aliceAddr := common.PrivKeyToAddress(aliceKey) alice := sdk.NewOpenAudioSDK(nodeUrl) alice.SetPrivKey(aliceKey) - if err := alice.Init(ctx); err != nil { - t.Fatalf("alice init: %v", err) - } bobKey, err := crypto.GenerateKey() if err != nil { @@ -99,46 +83,40 @@ func TestRewardPoolsLifecycle(t *testing.T) { t.Run("CreateRewardPool rejects non-pubkey rewards_manager_pubkey", func(t *testing.T) { // Pubkey shape check fires before signature verification, so the - // signature value here doesn't matter — we just need a non-base58 - // rewards_manager_pubkey to land at the shape rejection. + // rmPriv we pass here is signing for a different RM than the + // message claims — but the message's pubkey is rejected at the + // shape check before we get there. _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ RewardsManagerPubkey: "not-a-real-pubkey", Authorities: []string{aliceAddr}, - }, 999999) + }, rmPriv, 999999) if err == nil { t.Fatalf("expected non-pubkey rewards_manager_pubkey to be rejected") } }) - t.Run("CreateRewardPool rejects missing rm_owner_signature", func(t *testing.T) { - // Valid pubkey, valid authorities, signer ∈ authorities — but no - // signature. Must fail at the rm_owner_signature check. + t.Run("CreateRewardPool rejects signature signed by wrong RM keypair", func(t *testing.T) { + // Signature is well-formed and ed25519-valid — but produced by + // rmPriv against a body claiming otherRmPubkey. Verification key + // for the validator is otherRmPubkey, which doesn't match the + // rmPriv signing key, so verify fails. _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ RewardsManagerPubkey: otherRmPubkey, Authorities: []string{aliceAddr}, - }, 999999) - if err == nil { - t.Fatalf("expected missing rm_owner_signature to be rejected") - } - }) - - t.Run("CreateRewardPool rejects signature signed by wrong RM keypair", func(t *testing.T) { - // Signature is well-formed and the inner ed25519 verifies — but - // against the WRONG public key (rmPriv signs but message claims - // otherRmPubkey). Defends against attacker reusing a signature - // they made for their own RM against a different RM. - msg := signedCreateRewardPool(alice, otherRmPubkey, rmPriv, []string{aliceAddr}) - _, err := alice.Rewards.CreateRewardPool(ctx, msg, 999999) + }, rmPriv, 999999) if err == nil { t.Fatalf("expected mismatched-keypair signature to be rejected") } }) t.Run("CreateRewardPool requires signer in initial authorities", func(t *testing.T) { - // Mallory submits a perfectly-signed CreateRewardPool but isn't in - // the initial authorities. The RM-keypair gate passes; the - // signer-membership gate fails. - _, err := mallory.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(alice, otherRmPubkey, otherRmPriv, []string{aliceAddr, bobAddr}), 999999) + // Mallory submits a perfectly-signed CreateRewardPool (valid RM + // keypair) but isn't in the initial authorities. The RM-keypair + // gate passes; the signer-membership gate fails. + _, err := mallory.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: otherRmPubkey, + Authorities: []string{aliceAddr, bobAddr}, + }, otherRmPriv, 999999) if err == nil { t.Fatalf("expected CreateRewardPool to reject signer not in initial authorities") } @@ -148,7 +126,10 @@ func TestRewardPoolsLifecycle(t *testing.T) { }) t.Run("Alice creates the pool with [alice, bob]", func(t *testing.T) { - _, err := alice.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(alice, rmPubkey, rmPriv, []string{aliceAddr, bobAddr}), 999999) + _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{aliceAddr, bobAddr}, + }, rmPriv, 999999) if err != nil { t.Fatalf("create pool: %v", err) } @@ -166,7 +147,10 @@ func TestRewardPoolsLifecycle(t *testing.T) { }) t.Run("CreateRewardPool with duplicate rewards_manager_pubkey is rejected", func(t *testing.T) { - _, err := alice.Rewards.CreateRewardPool(ctx, signedCreateRewardPool(alice, rmPubkey, rmPriv, []string{aliceAddr}), 999999) + _, err := alice.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{ + RewardsManagerPubkey: rmPubkey, + Authorities: []string{aliceAddr}, + }, rmPriv, 999999) if err == nil { t.Fatalf("expected duplicate rewards_manager_pubkey to be rejected") } diff --git a/pkg/rewards/reward_pool.go b/pkg/rewards/reward_pool.go index 2ff04340..5d72e8f5 100644 --- a/pkg/rewards/reward_pool.go +++ b/pkg/rewards/reward_pool.go @@ -1,7 +1,6 @@ package rewards import ( - "crypto/ed25519" "sort" "strings" ) @@ -28,46 +27,3 @@ func CanonicalAuthorities(authorities []string) []string { sort.Strings(out) return out } - -// CreateRewardPoolOwnerSignatureDomain is the domain-separation prefix -// used in the canonical CreateRewardPool authorization payload. Any -// future ed25519 signing scheme that re-uses an RM keypair MUST use a -// different domain prefix to avoid signature replay across schemes. -const CreateRewardPoolOwnerSignatureDomain = "audius:create-reward-pool:" - -// CanonicalCreateRewardPoolPayload returns the bytes the RM keypair signs -// to authorize a CreateRewardPool transaction. Format: -// -// "audius:create-reward-pool:" + chain_id + ":" + rm_pubkey_b58 + -// ":" + sorted_lowercased_authorities.join(",") -// -// Authorities are canonicalized so that an attacker cannot grind a -// different message-byte order to produce a different signature for the -// same logical authority set. chain_id is included to prevent cross- -// chain replay; rm_pubkey is included to bind the signature to the -// specific RM being registered. -// -// Both the validator (signature verification) and any client that -// produces signatures (launchpad relay, integration tests) must use this -// helper so encodings stay synchronized. -func CanonicalCreateRewardPoolPayload(chainID, rmPubkey string, authorities []string) []byte { - canon := CanonicalAuthorities(authorities) - var b strings.Builder - b.WriteString(CreateRewardPoolOwnerSignatureDomain) - b.WriteString(chainID) - b.WriteByte(':') - b.WriteString(rmPubkey) - b.WriteByte(':') - b.WriteString(strings.Join(canon, ",")) - return []byte(b.String()) -} - -// SignCreateRewardPool produces an ed25519 signature over the canonical -// authorization payload for a CreateRewardPool tx, suitable for placing -// in CreateRewardPool.RmOwnerSignature. Used by clients that hold the RM -// keypair (the launchpad relay deterministically derives it from -// (launchpadDeterministicSecret, mint); integration tests generate fresh -// keypairs via ed25519.GenerateKey). -func SignCreateRewardPool(privKey ed25519.PrivateKey, chainID, rmPubkey string, authorities []string) []byte { - return ed25519.Sign(privKey, CanonicalCreateRewardPoolPayload(chainID, rmPubkey, authorities)) -} diff --git a/pkg/sdk/rewards/rewards.go b/pkg/sdk/rewards/rewards.go index 81111fd8..d319a308 100644 --- a/pkg/sdk/rewards/rewards.go +++ b/pkg/sdk/rewards/rewards.go @@ -3,6 +3,7 @@ package rewards import ( "context" "crypto/ecdsa" + "crypto/ed25519" "errors" "connectrpc.com/connect" @@ -48,7 +49,14 @@ func (r *Rewards) signAndSendReward(ctx context.Context, body *v1.RewardBody) (s return resp.Msg.GetTransaction().GetHash(), nil } -func (r *Rewards) signAndSendRewardPool(ctx context.Context, body *v1.RewardPoolBody) (string, error) { +// signAndSendRewardPool signs the body with the envelope signer's +// secp256k1 key and submits. rmOwnerSig, when non-nil, is placed in the +// envelope's rm_owner_signature field. It is required when the body's +// action is CreateRewardPool (the validator rejects unsigned creates as +// frontrunning) and ignored otherwise. Callers produce the ed25519 +// signature over common.ProtoSignableBytes(body) using the RM keypair — +// see CreateRewardPool below for the canonical helper. +func (r *Rewards) signAndSendRewardPool(ctx context.Context, body *v1.RewardPoolBody, rmOwnerSig []byte) (string, error) { sig, err := common.ProtoSign(r.privKey, body) if err != nil { return "", err @@ -56,7 +64,11 @@ func (r *Rewards) signAndSendRewardPool(ctx context.Context, body *v1.RewardPool tx := &v1.SendTransactionRequest{ Transaction: &v1.SignedTransaction{ Transaction: &v1.SignedTransaction_RewardPool{ - RewardPool: &v1.RewardPoolMessage{Body: body, Signature: sig}, + RewardPool: &v1.RewardPoolMessage{ + Body: body, + Signature: sig, + RmOwnerSignature: rmOwnerSig, + }, }, }, } @@ -119,15 +131,28 @@ func (r *Rewards) GetRewards(ctx context.Context, claim_authority string) (*v1.G // CreateRewardPool submits a CreateRewardPool transaction. The pool is // keyed by msg.RewardsManagerPubkey (the Solana reward manager pubkey it // will govern, base58 32 bytes); subsequent SetRewardPoolAuthorities / -// CreateReward calls reference the pool by this same value, so callers -// can compose dependent transactions without round-tripping through -// GetRewardPool. Returns the cometbft tx hash. -func (r *Rewards) CreateRewardPool(ctx context.Context, msg *v1.CreateRewardPool, deadlineBlockHeight int64) (string, error) { +// CreateReward calls reference the pool by this same value. +// +// rmKey is the ed25519 PRIVATE key whose public key is +// msg.RewardsManagerPubkey — i.e., the Solana RM state account's +// keypair. For launchpad-minted coins, the relay derives this from +// (launchpadDeterministicSecret, mint). The validator requires the +// envelope-level rm_owner_signature to verify against +// msg.RewardsManagerPubkey, so possession of the matching ed25519 secret +// is the proof-of-RM-control that defeats pool-creation frontrunning. +// +// Returns the cometbft tx hash. +func (r *Rewards) CreateRewardPool(ctx context.Context, msg *v1.CreateRewardPool, rmKey ed25519.PrivateKey, deadlineBlockHeight int64) (string, error) { body := &v1.RewardPoolBody{ DeadlineBlockHeight: deadlineBlockHeight, Action: &v1.RewardPoolBody_Create{Create: msg}, } - return r.signAndSendRewardPool(ctx, body) + bodyBytes, err := common.ProtoSignableBytes(body) + if err != nil { + return "", err + } + rmSig := ed25519.Sign(rmKey, bodyBytes) + return r.signAndSendRewardPool(ctx, body, rmSig) } // GetRewardPool fetches a pool by its rewards_manager_pubkey (Solana RM @@ -142,13 +167,15 @@ func (r *Rewards) GetRewardPool(ctx context.Context, rewardsManagerPubkey string // SetRewardPoolAuthorities replaces the pool's authority set wholesale. The // caller composes the desired list (current minus the one to remove, current -// plus the one to add, etc.); add and remove are derived views. +// plus the one to add, etc.); add and remove are derived views. No RM +// keypair signature is needed — rotation is gated by signer ∈ current +// pool authorities, by design (the launchpad needn't be online to rotate). func (r *Rewards) SetRewardPoolAuthorities(ctx context.Context, msg *v1.SetRewardPoolAuthorities, deadlineBlockHeight int64) (string, error) { body := &v1.RewardPoolBody{ DeadlineBlockHeight: deadlineBlockHeight, Action: &v1.RewardPoolBody_SetAuthorities{SetAuthorities: msg}, } - return r.signAndSendRewardPool(ctx, body) + return r.signAndSendRewardPool(ctx, body, nil) } // GetRewardSenderAttestation requests an attestation that the validator will diff --git a/proto/core/v1/types.proto b/proto/core/v1/types.proto index e29e10d2..b4a68f59 100644 --- a/proto/core/v1/types.proto +++ b/proto/core/v1/types.proto @@ -524,9 +524,10 @@ message RewardMessage { } // RewardBody is the signed payload. deadline_block_height bounds the -// signature's validity (the validator rejects after expiry); the action is -// the actual operation. The oneof tag discriminates Create from Delete so -// two actions with otherwise-identical inner shapes produce different bytes. +// signature's validity (the validator rejects after expiry); the action +// is the actual operation. The oneof tag discriminates Create from +// Delete so two actions with otherwise-identical inner shapes produce +// different bytes. message RewardBody { int64 deadline_block_height = 1; oneof action { @@ -568,12 +569,33 @@ message DeleteReward { } // RewardPoolMessage is the wire envelope for pool-management transactions. -// Same shape as RewardMessage: body + signature. +// Two signatures cover the same body bytes: +// - signature: secp256k1 (eth) recoverable signature, identifies the +// fee-paying tx submitter who must be in the pool's current +// authority set (Create: initial authorities; SetAuthorities: +// existing pool authorities). +// - rm_owner_signature: ed25519 signature, REQUIRED when body.action +// is Create. Verification key IS body.create.rewards_manager_pubkey +// (the Solana RM state account is itself an ed25519 keypair). Proves +// possession of the RM keypair, defeating pool-creation frontrunning +// by an observer who watches Solana RM init events but lacks the +// launchpad's deterministic-secret. Ignored for SetAuthorities +// because rotation is meant to work without the launchpad. message RewardPoolMessage { RewardPoolBody body = 1; string signature = 2; + bytes rm_owner_signature = 3; } +// RewardPoolBody is the signed payload. deadline_block_height bounds the +// signature's validity; action discriminates Create from SetAuthorities +// so two otherwise-identical messages produce different bytes. +// +// Cross-chain replay isn't a threat surface here: each environment's +// launchpad uses a different deterministic secret, so the same +// rewards_manager_pubkey cannot be derived on more than one chain. A +// captured CreateRewardPool replayed on a different chain refers to an +// RM that doesn't exist there. message RewardPoolBody { int64 deadline_block_height = 1; oneof action { @@ -591,19 +613,9 @@ message CreateRewardPool { string rewards_manager_pubkey = 1; // Initial set of eth addresses authorized to attest for rewards in this // pool. The recovered signer must be a member of this list. Each entry - // must be a valid eth hex address. + // must be a valid eth hex address. Frontrunning defense lives at the + // envelope level (RewardPoolMessage.rm_owner_signature). repeated string authorities = 2; - // ed25519 signature over the canonical payload - // "audius:create-reward-pool:" + chain_id + ":" + rewards_manager_pubkey - // + ":" + sorted_lowercased_authorities.join(",") - // produced by the ed25519 private key whose public key IS - // rewards_manager_pubkey. Proves possession of the Solana reward - // manager state account's keypair, which only the legitimate creator - // (the launchpad relay, deterministically deriving the keypair from - // (launchpadDeterministicSecret, mint)) holds. Defends against - // frontrunning where an observer races to register a pool for a - // freshly-minted RM with attacker-chosen authorities. Required. - bytes rm_owner_signature = 3; } // SetRewardPoolAuthorities replaces the pool's authority set wholesale. The From c2fdf9adf0bec0b28180e56f51624feebe09d7ad Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Tue, 12 May 2026 11:11:14 -0500 Subject: [PATCH 5/9] =?UTF-8?q?Renumber=20reward-pools=20migration=2000033?= =?UTF-8?q?=20=E2=86=92=2000034?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main shipped a different 00033 (drop_redundant_tx_hash_index, #205) while this branch was open. Bump ours to 00034 to keep migration ordering unambiguous; content is unchanged. Co-Authored-By: Claude Opus 4.7 --- .../migrations/{00033_reward_pools.sql => 00034_reward_pools.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/core/db/sql/migrations/{00033_reward_pools.sql => 00034_reward_pools.sql} (100%) diff --git a/pkg/core/db/sql/migrations/00033_reward_pools.sql b/pkg/core/db/sql/migrations/00034_reward_pools.sql similarity index 100% rename from pkg/core/db/sql/migrations/00033_reward_pools.sql rename to pkg/core/db/sql/migrations/00034_reward_pools.sql From eee1897fdee507589c1cf1f2149fab291a730522 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Tue, 12 May 2026 09:26:24 -0700 Subject: [PATCH 6/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/core/server/reward_pools.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/core/server/reward_pools.go b/pkg/core/server/reward_pools.go index 7ac09c53..d8ee066b 100644 --- a/pkg/core/server/reward_pools.go +++ b/pkg/core/server/reward_pools.go @@ -46,8 +46,8 @@ var ErrRewardPoolOwnerSignatureInvalid = errors.New("reward pool owner signature // // bodyBytes are the deterministic-protobuf marshaling of RewardPoolBody — // the same bytes the secp256k1 envelope signature covers. This means -// chain_id (in the body) and deadline_block_height are both implicitly -// covered, defeating cross-chain replay and stale-signature replay +// fields present in the body, including deadline_block_height, are +// implicitly covered by both signatures, defeating stale-signature replay // without a separate signed-string format. func verifyRewardPoolOwnerSignature(rmPubkey string, bodyBytes, signature []byte) error { if len(signature) != ed25519.SignatureSize { From 00ff336c81f168404324c0e5c0008689641b3240 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Tue, 12 May 2026 09:26:48 -0700 Subject: [PATCH 7/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- proto/core/v1/types.proto | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proto/core/v1/types.proto b/proto/core/v1/types.proto index 976917a0..d450d1e4 100644 --- a/proto/core/v1/types.proto +++ b/proto/core/v1/types.proto @@ -516,12 +516,13 @@ message GetPIEResponse { } // RewardMessage is the wire envelope: it bundles a signed body with its -// signature. The body is what gets signed (deterministic protobuf bytes); -// the signature lives alongside the body, not inside it, so signing has no -// chicken-and-egg. +// signature. The body is signed by first taking the deterministic protobuf +// marshaling of body, then computing sha256 over those bytes, and signing +// that digest; the signature lives alongside the body, not inside it, so +// signing has no chicken-and-egg. message RewardMessage { RewardBody body = 1; - string signature = 2; // signature over the deterministic marshaling of body + string signature = 2; // signature over sha256(deterministic marshaling of body) } // RewardBody is the signed payload. deadline_block_height bounds the From 8012af39d42ec80a98709232fb0cc410efc2c6fc Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Tue, 12 May 2026 12:33:13 -0500 Subject: [PATCH 8/9] Address PR #254 review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six Copilot-flagged items from the latest review: 1. Proto signature comments now spell out the actual pre-hashing: - RewardMessage.signature: secp256k1 over sha256(body bytes). - RewardPoolMessage.signature: same. - RewardPoolMessage.rm_owner_signature: ed25519 over body bytes directly (ed25519 hashes internally — do NOT pre-hash). Lets non-Go clients reproduce signatures without reading pkg/common/crypto.go. 2. Split validateRewardsManagerPubkey: - validateRewardsManagerPubkeyShape (new): non-empty, no whitespace, base58, 32 bytes. Pure shape. For read paths and rotation paths. - validateRewardsManagerPubkey (existing): shape + AUDIO denylist. Only for write paths (CreateRewardPool, CreateReward). Switched call sites: - validateSetRewardPoolAuthorities / finalizeSetRewardPoolAuthorities → Shape. SetAuthorities targets an existing pool; AUDIO has no pool by construction, so checkPoolAuthorization surfaces the case as "pool not found" rather than the misleading "is reserved". - GetRewardPool → Shape. Probing GetRewardPool(AudioRM) now returns a clean NotFound instead of InvalidArgument. - GetRewardSenderAttestation / GetDeleteRewardSenderAttestation → add Shape validation up front so malformed pubkeys return a clear InvalidArgument instead of falling through to ErrSenderGateUnknownRM (which is for valid- shape-but-unmapped RMs). 3. Removed the stale "chain_id is covered by signed body bytes" reference in validateCreateRewardPool's comment — the body doesn't carry chain_id, and that's intentional (cross-chain replay isn't a threat because per-env launchpad secrets prevent the same rewards_manager_pubkey from existing on more than one chain). 4. SDK CreateRewardPool now validates rmKey length up front and returns a typed error instead of panicking inside ed25519.Sign for callers that pass nil / hex-decode-wrong / public-key-by-mistake. 5. GetRewardAttestation now TrimSpaces eth_recipient_address, reward_address, and claim_authority at the boundary so surrounding whitespace returns a clean InvalidArgument here instead of a confusing hex-decode error deeper in RewardClaim.Compile. Co-Authored-By: Claude Opus 4.7 --- pkg/api/core/v1/types.pb.go | 48 ++++++++++++++++--------- pkg/core/server/connect.go | 38 +++++++++++++------- pkg/core/server/reward_pools.go | 62 ++++++++++++++++++++++----------- pkg/sdk/rewards/rewards.go | 7 ++++ proto/core/v1/types.proto | 39 ++++++++++++++------- 5 files changed, 132 insertions(+), 62 deletions(-) diff --git a/pkg/api/core/v1/types.pb.go b/pkg/api/core/v1/types.pb.go index ecf0e917..cbf92598 100644 --- a/pkg/api/core/v1/types.pb.go +++ b/pkg/api/core/v1/types.pb.go @@ -4111,16 +4111,23 @@ func (x *GetPIEResponse) GetPie() *v1beta11.PieMessage { } // RewardMessage is the wire envelope: it bundles a signed body with its -// signature. The body is what gets signed (deterministic protobuf bytes); -// the signature lives alongside the body, not inside it, so signing has no -// chicken-and-egg. +// signature. The body is signed by first taking the deterministic protobuf +// marshaling of body, then computing sha256 over those bytes, and signing +// that digest; the signature lives alongside the body, not inside it, so +// signing has no chicken-and-egg. type RewardMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Body *RewardBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` - Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` // signature over the deterministic marshaling of body + Body *RewardBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + // secp256k1 (Ethereum-style) signature, hex-encoded. The signed + // digest is sha256(deterministic-protobuf-marshaling of body) — i.e., + // marshal body with proto.MarshalOptions{Deterministic: true}, then + // sha256-hash, then crypto.Sign as eth-recoverable. Non-Go clients + // must reproduce this pre-hash (sha256, NOT keccak256) to produce a + // signature the validator will accept. + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *RewardMessage) Reset() { @@ -4388,18 +4395,25 @@ func (x *DeleteReward) GetAddress() string { } // RewardPoolMessage is the wire envelope for pool-management transactions. -// Two signatures cover the same body bytes: -// - signature: secp256k1 (eth) recoverable signature, identifies the -// fee-paying tx submitter who must be in the pool's current -// authority set (Create: initial authorities; SetAuthorities: -// existing pool authorities). -// - rm_owner_signature: ed25519 signature, REQUIRED when body.action -// is Create. Verification key IS body.create.rewards_manager_pubkey -// (the Solana RM state account is itself an ed25519 keypair). Proves -// possession of the RM keypair, defeating pool-creation frontrunning -// by an observer who watches Solana RM init events but lacks the -// launchpad's deterministic-secret. Ignored for SetAuthorities -// because rotation is meant to work without the launchpad. +// Two signatures cover the same body bytes (`deterministic-protobuf- +// marshaling of body`); the schemes differ in pre-hashing: +// +// - signature: secp256k1 (eth) recoverable signature, hex-encoded. +// Pre-hash is sha256(body_bytes) — see RewardMessage.signature for +// the cross-language reproduction recipe. Identifies the fee-paying +// tx submitter who must be in the pool's current authority set +// (Create: initial authorities; SetAuthorities: existing pool +// authorities). +// - rm_owner_signature: ed25519 signature over body_bytes directly +// (ed25519 handles its own internal hashing — sign body_bytes raw, +// do NOT pre-hash). REQUIRED when body.action is Create. +// Verification key IS body.create.rewards_manager_pubkey (the +// Solana RM state account is itself an ed25519 keypair). Proves +// possession of the RM keypair, defeating pool-creation +// frontrunning by an observer who watches Solana RM init events +// but lacks the launchpad's deterministic-secret. Ignored for +// SetAuthorities because rotation is meant to work without the +// launchpad. type RewardPoolMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/pkg/core/server/connect.go b/pkg/core/server/connect.go index b5a74a3b..de822ba4 100644 --- a/pkg/core/server/connect.go +++ b/pkg/core/server/connect.go @@ -1071,11 +1071,16 @@ func (c *CoreService) GetStatus(ctx context.Context, _ *connect.Request[v1.GetSt // rotating an authority out via SetRewardPoolAuthorities immediately // revokes their ability to authenticate claim attestations. func (c *CoreService) GetRewardAttestation(ctx context.Context, req *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) { - ethRecipientAddress := req.Msg.EthRecipientAddress + // Trim user-supplied addresses up front. RewardClaim.Compile + // hex-decodes ethRecipientAddress / claimAuthority and surfaces + // surrounding whitespace as a confusing "failed to decode" error + // deeper in the flow; trimming here lets the same input return + // InvalidArgument at the boundary instead. + ethRecipientAddress := strings.TrimSpace(req.Msg.EthRecipientAddress) if ethRecipientAddress == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("eth_recipient_address is required")) } - rewardAddress := req.Msg.RewardAddress + rewardAddress := strings.TrimSpace(req.Msg.RewardAddress) if rewardAddress == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward_address is required")) } @@ -1086,7 +1091,7 @@ func (c *CoreService) GetRewardAttestation(ctx context.Context, req *connect.Req if len(specifier) > 256 { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("specifier too long")) } - claimAuthority := req.Msg.ClaimAuthority + claimAuthority := strings.TrimSpace(req.Msg.ClaimAuthority) if claimAuthority == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("claim_authority is required")) } @@ -1265,12 +1270,13 @@ func (c *CoreService) GetReward(ctx context.Context, req *connect.Request[v1.Get // There is no separate synthetic-pool surface to filter out. func (c *CoreService) GetRewardPool(ctx context.Context, req *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) { // Normalize and shape-validate up front so malformed input returns - // InvalidArgument deterministically instead of falling through to a DB - // lookup that returns NotFound. Reuses validateRewardsManagerPubkey - // (the same validator the CreateRewardPool / SetRewardPoolAuthorities - // tx path uses) so the contract is identical across read and write. + // InvalidArgument deterministically instead of falling through to a + // DB lookup that returns NotFound. Use the shape-only validator + // here, NOT the create-time AUDIO denylist: a caller probing + // GetRewardPool(AudioRM) should get a clean NotFound, not a "pool + // reserved" InvalidArgument that hides whether a pool exists. rewardsManagerPubkey := strings.TrimSpace(req.Msg.RewardsManagerPubkey) - if err := validateRewardsManagerPubkey(rewardsManagerPubkey); err != nil { + if err := validateRewardsManagerPubkeyShape(rewardsManagerPubkey); err != nil { return nil, connect.NewError(connect.CodeInvalidArgument, err) } pool, err := c.core.db.GetRewardPool(ctx, rewardsManagerPubkey) @@ -1856,8 +1862,12 @@ func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *conne } rewardsManagerPubkey := strings.TrimSpace(req.Msg.RewardsManagerPubkey) - if rewardsManagerPubkey == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward manager pubkey is required")) + if err := validateRewardsManagerPubkeyShape(rewardsManagerPubkey); err != nil { + // Shape-validate up front so a malformed pubkey returns + // InvalidArgument with a clear message instead of falling through + // to senderGateForRM and surfacing as ErrSenderGateUnknownRM + // (which is misleading — it's invalid input, not an unknown RM). + return nil, connect.NewError(connect.CodeInvalidArgument, err) } pool, isPoolGated, err := c.senderGateForRM(ctx, rewardsManagerPubkey) @@ -1916,8 +1926,12 @@ func (c *CoreService) GetDeleteRewardSenderAttestation(ctx context.Context, req } rewardsManagerPubkey := strings.TrimSpace(req.Msg.RewardsManagerPubkey) - if rewardsManagerPubkey == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("reward manager pubkey is required")) + if err := validateRewardsManagerPubkeyShape(rewardsManagerPubkey); err != nil { + // Shape-validate up front so a malformed pubkey returns + // InvalidArgument with a clear message instead of falling through + // to senderGateForRM and surfacing as ErrSenderGateUnknownRM + // (which is misleading — it's invalid input, not an unknown RM). + return nil, connect.NewError(connect.CodeInvalidArgument, err) } pool, isPoolGated, err := c.senderGateForRM(ctx, rewardsManagerPubkey) diff --git a/pkg/core/server/reward_pools.go b/pkg/core/server/reward_pools.go index d8ee066b..88d4294d 100644 --- a/pkg/core/server/reward_pools.go +++ b/pkg/core/server/reward_pools.go @@ -109,7 +109,10 @@ func (s *Server) isValidRewardPoolTransaction(ctx context.Context, signedTx *cor // common.ProtoRecover (which marshals deterministically); the ed25519 // path verifies against common.ProtoSignableBytes(body) directly. body // covers deadline_block_height and the action oneof, so stale-deadline -// replay is blocked for both signatures together. +// replay is blocked for both signatures together. Cross-chain replay +// isn't a threat: each environment's launchpad uses a different +// deterministic secret, so the same rewards_manager_pubkey cannot exist +// on more than one chain. func (s *Server) validateCreateRewardPool(ctx context.Context, envelope *corev1.RewardPoolMessage, msg *corev1.CreateRewardPool, signer string) error { if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { return err @@ -154,15 +157,13 @@ func validateAuthorityList(addrs []string) error { return nil } -// validateRewardsManagerPubkey checks the wire shape of a Solana reward -// manager pubkey: non-empty, base58-decodable, exactly 32 bytes. -// -// First-class pools must use a real RM pubkey because PR3's -// sender-attestation gate uses the same value to bind the pool↔RM. PR1's -// backfill resolves each existing reward row to a real RM via the -// launchpad_authority_rm mapping, so there are no synthetic-pool -// identifiers in production state to special-case here. -func validateRewardsManagerPubkey(pubkey string) error { +// validateRewardsManagerPubkeyShape checks the wire shape of a Solana +// reward manager pubkey: non-empty, no surrounding whitespace, base58- +// decodable, exactly 32 bytes. Pure shape validation — no policy. Use +// from read paths and rotation paths where the AUDIO RM is a legitimate +// input that should round-trip cleanly (read paths return NotFound; +// sender-gate falls back to validator/AAO). +func validateRewardsManagerPubkeyShape(pubkey string) error { if pubkey == "" { return fmt.Errorf("%w: rewards_manager_pubkey is required", ErrRewardMessageValidation) } @@ -176,14 +177,30 @@ func validateRewardsManagerPubkey(pubkey string) error { if len(bytes) != solanaPubkeyByteLen { return fmt.Errorf("%w: rewards_manager_pubkey must decode to %d bytes; got %d", ErrRewardMessageValidation, solanaPubkeyByteLen, len(bytes)) } - // Reserved-RM denylist: AUDIO continues to be governed by the - // network-wide validator/AAO trust set in PR3's sender-attestation - // gate (the gate falls back to validator/AAO for any RM that has no - // pool). Allowing a pool to be created for the AUDIO RM would shift - // AUDIO sender attestations to pool-controlled authorities — - // defeating the AUDIO trust model. Trim defensively: if these - // per-env constants ever start being populated from env vars or - // config, surrounding whitespace would silently disable the check. + return nil +} + +// validateRewardsManagerPubkey is the WRITE-path validator: shape + +// the AUDIO reserved-RM denylist. Use from CreateRewardPool and +// CreateReward. Allowing a pool to be created for the AUDIO RM would +// shift AUDIO sender attestations to pool-controlled authorities, +// defeating the AUDIO trust model — AUDIO is intentionally governed by +// the network-wide validator/AAO trust set in PR3's sender-attestation +// gate (the gate falls back to validator/AAO for any RM that has no +// pool). +// +// First-class pools must use a real RM pubkey because PR3's +// sender-attestation gate uses the same value to bind the pool↔RM. PR1's +// backfill resolves each existing reward row to a real RM via the +// launchpad_authority_rm mapping, so there are no synthetic-pool +// identifiers in production state to special-case here. +func validateRewardsManagerPubkey(pubkey string) error { + if err := validateRewardsManagerPubkeyShape(pubkey); err != nil { + return err + } + // Trim defensively on the configured side: if these per-env + // constants ever start being populated from env vars or config, + // surrounding whitespace would silently disable the check. if audioRM := strings.TrimSpace(config.AudioRewardsManagerPubkey()); audioRM != "" && pubkey == audioRM { return fmt.Errorf("%w: rewards_manager_pubkey is reserved (AUDIO); pools cannot be created for it", ErrRewardMessageValidation) } @@ -199,7 +216,12 @@ func validateRewardsManagerPubkey(pubkey string) error { // addresses (otherwise the pool can be rotated into a permanently-orphaned // state). func (s *Server) validateSetRewardPoolAuthorities(ctx context.Context, msg *corev1.SetRewardPoolAuthorities, signer string) error { - if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { + // Shape-only here, not the AUDIO denylist: SetRewardPoolAuthorities + // targets an existing pool, and AUDIO has no pool by construction + // (validateCreateRewardPool refuses), so checkPoolAuthorization + // surfaces the AUDIO case as the more accurate "pool not found" + // rather than the create-time "is reserved" message. + if err := validateRewardsManagerPubkeyShape(msg.RewardsManagerPubkey); err != nil { return err } if err := validateAuthorityList(msg.Authorities); err != nil { @@ -282,7 +304,7 @@ func (s *Server) finalizeCreateRewardPool(ctx context.Context, envelope *corev1. // to live and prevents an orphaned/empty-authority pool from being // written. func (s *Server) finalizeSetRewardPoolAuthorities(ctx context.Context, msg *corev1.SetRewardPoolAuthorities, signer string) error { - if err := validateRewardsManagerPubkey(msg.RewardsManagerPubkey); err != nil { + if err := validateRewardsManagerPubkeyShape(msg.RewardsManagerPubkey); err != nil { return err } if err := validateAuthorityList(msg.Authorities); err != nil { diff --git a/pkg/sdk/rewards/rewards.go b/pkg/sdk/rewards/rewards.go index d319a308..ad9494da 100644 --- a/pkg/sdk/rewards/rewards.go +++ b/pkg/sdk/rewards/rewards.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "crypto/ed25519" "errors" + "fmt" "connectrpc.com/connect" v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" @@ -143,6 +144,12 @@ func (r *Rewards) GetRewards(ctx context.Context, claim_authority string) (*v1.G // // Returns the cometbft tx hash. func (r *Rewards) CreateRewardPool(ctx context.Context, msg *v1.CreateRewardPool, rmKey ed25519.PrivateKey, deadlineBlockHeight int64) (string, error) { + // ed25519.Sign panics on wrong-length keys; surface as a typed error + // instead so callers passing nil / hex-decoded-wrong / public-key-by- + // mistake see a recoverable failure rather than a runtime crash. + if len(rmKey) != ed25519.PrivateKeySize { + return "", fmt.Errorf("rmKey is %d bytes; want ed25519 private key (%d bytes)", len(rmKey), ed25519.PrivateKeySize) + } body := &v1.RewardPoolBody{ DeadlineBlockHeight: deadlineBlockHeight, Action: &v1.RewardPoolBody_Create{Create: msg}, diff --git a/proto/core/v1/types.proto b/proto/core/v1/types.proto index d450d1e4..a0a21178 100644 --- a/proto/core/v1/types.proto +++ b/proto/core/v1/types.proto @@ -522,7 +522,13 @@ message GetPIEResponse { // signing has no chicken-and-egg. message RewardMessage { RewardBody body = 1; - string signature = 2; // signature over sha256(deterministic marshaling of body) + // secp256k1 (Ethereum-style) signature, hex-encoded. The signed + // digest is sha256(deterministic-protobuf-marshaling of body) — i.e., + // marshal body with proto.MarshalOptions{Deterministic: true}, then + // sha256-hash, then crypto.Sign as eth-recoverable. Non-Go clients + // must reproduce this pre-hash (sha256, NOT keccak256) to produce a + // signature the validator will accept. + string signature = 2; } // RewardBody is the signed payload. deadline_block_height bounds the @@ -571,18 +577,25 @@ message DeleteReward { } // RewardPoolMessage is the wire envelope for pool-management transactions. -// Two signatures cover the same body bytes: -// - signature: secp256k1 (eth) recoverable signature, identifies the -// fee-paying tx submitter who must be in the pool's current -// authority set (Create: initial authorities; SetAuthorities: -// existing pool authorities). -// - rm_owner_signature: ed25519 signature, REQUIRED when body.action -// is Create. Verification key IS body.create.rewards_manager_pubkey -// (the Solana RM state account is itself an ed25519 keypair). Proves -// possession of the RM keypair, defeating pool-creation frontrunning -// by an observer who watches Solana RM init events but lacks the -// launchpad's deterministic-secret. Ignored for SetAuthorities -// because rotation is meant to work without the launchpad. +// Two signatures cover the same body bytes (`deterministic-protobuf- +// marshaling of body`); the schemes differ in pre-hashing: +// +// - signature: secp256k1 (eth) recoverable signature, hex-encoded. +// Pre-hash is sha256(body_bytes) — see RewardMessage.signature for +// the cross-language reproduction recipe. Identifies the fee-paying +// tx submitter who must be in the pool's current authority set +// (Create: initial authorities; SetAuthorities: existing pool +// authorities). +// - rm_owner_signature: ed25519 signature over body_bytes directly +// (ed25519 handles its own internal hashing — sign body_bytes raw, +// do NOT pre-hash). REQUIRED when body.action is Create. +// Verification key IS body.create.rewards_manager_pubkey (the +// Solana RM state account is itself an ed25519 keypair). Proves +// possession of the RM keypair, defeating pool-creation +// frontrunning by an observer who watches Solana RM init events +// but lacks the launchpad's deterministic-secret. Ignored for +// SetAuthorities because rotation is meant to work without the +// launchpad. message RewardPoolMessage { RewardPoolBody body = 1; string signature = 2; From 7b209afe87e1e5068d1f80d8713ea5bf09be1112 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Tue, 12 May 2026 13:44:40 -0500 Subject: [PATCH 9/9] Address PR #254 review feedback (round 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six items from raymondjacobson: 1. examples/rewards/main.go: drop REWARDS_MANAGER_SECRET_HEX env var and generate a fresh ed25519 keypair inline. Strip the explainer comments — the simpler example is self-documenting. 2. pkg/core/config/rewards.go: remove the staging-specific AUDIO RM constant and the long comment about why staging is empty. The AudioRewardsManagerPubkey() switch no longer special-cases stage, so staging falls through to "" via the default branch, which the denylist treats as "no enforcement." The reward_pools_test save/ restore no longer touches StageAudioRewardsManagerPubkey. 3. 00034_reward_pools.sql backfill comment: drop "leaked-key" framing, replace with neutral "additional entries." 4 + 6. Sweep PR1/PR2/PR3/PR #225 references out of all bundle code and comments — these labeled stacked-PR boundaries that no longer exist now that the work is bundled. Phrasing now describes what the code does, not which PR introduced it. Touched: connect.go, reward_pools.go, rewards.go, rewards_legacy.go, reads.sql, migration, proto, integration test. 5. reward_pools.go: drop the case-insensitive contains() helper and use slices.Contains across all call sites. Pool authorities are already canonicalized (lowercase) on write via CanonicalAuthorities, so callers just lowercase the needle. Removes ~10 lines and a custom helper in favor of stdlib. Co-Authored-By: Claude Opus 4.7 --- examples/rewards/main.go | 33 +++------------- pkg/api/core/v1/types.pb.go | 7 ++-- pkg/core/config/rewards.go | 39 +++++-------------- pkg/core/db/reads.sql.go | 14 +++---- .../db/sql/migrations/00034_reward_pools.sql | 33 ++++++++-------- pkg/core/db/sql/reads.sql | 14 +++---- pkg/core/server/connect.go | 27 ++++++------- pkg/core/server/reward_pools.go | 31 ++++++--------- pkg/core/server/reward_pools_test.go | 5 +-- pkg/core/server/rewards.go | 10 +++-- pkg/core/server/rewards_legacy.go | 32 ++++++++------- pkg/integration_tests/13_reward_pools_test.go | 2 +- proto/core/v1/types.proto | 7 ++-- 13 files changed, 105 insertions(+), 149 deletions(-) diff --git a/examples/rewards/main.go b/examples/rewards/main.go index 8b235596..51bfaafb 100644 --- a/examples/rewards/main.go +++ b/examples/rewards/main.go @@ -3,7 +3,7 @@ package main import ( "context" "crypto/ed25519" - "encoding/hex" + "crypto/rand" "fmt" "log" "os" @@ -41,38 +41,15 @@ func main() { currentHeight := resp.Msg.ChainInfo.CurrentHeight deadline := currentHeight + 100 - // CreateRewardPool requires possession of the RM's ed25519 keypair — - // the same keypair that signed the InitRewardManager instruction on - // Solana. The launchpad relay derives this from - // (launchpadDeterministicSecret, mint) and so always has it. - // - // REWARDS_MANAGER_SECRET_HEX is the 64-byte ed25519 secret key, hex- - // encoded. We derive the public key (which IS the rewards_manager_pubkey, - // base58-encoded) from it. - secretHex := os.Getenv("REWARDS_MANAGER_SECRET_HEX") - if secretHex == "" { - log.Fatalf("REWARDS_MANAGER_SECRET_HEX environment variable is not set (64-byte ed25519 secret, hex-encoded)") - } - rmSecret, err := hex.DecodeString(secretHex) + rmPubKey, rmPrivKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { - log.Fatalf("Failed to decode REWARDS_MANAGER_SECRET_HEX: %v", err) - } - if len(rmSecret) != ed25519.PrivateKeySize { - log.Fatalf("REWARDS_MANAGER_SECRET_HEX must decode to %d bytes; got %d", ed25519.PrivateKeySize, len(rmSecret)) + log.Fatalf("Failed to generate RM keypair: %v", err) } - rmPrivKey := ed25519.PrivateKey(rmSecret) - rewardsManagerPubkey := base58.Encode(rmPrivKey.Public().(ed25519.PublicKey)) + rewardsManagerPubkey := base58.Encode(rmPubKey) - // First-class CreateReward requires an existing pool keyed by the - // reward manager pubkey. Create one — fail loudly on any error so the - // next call doesn't proceed against a broken setup. If the pool was - // created in a previous run, this will surface as a "pool already - // exists" error and the example needs to be rerun against a fresh RM - // pubkey (or the existing pool's tx hash recorded for the reuse path). - authorities := []string{oap.Address()} if _, err := oap.Rewards.CreateRewardPool(context.Background(), &v1.CreateRewardPool{ RewardsManagerPubkey: rewardsManagerPubkey, - Authorities: authorities, + Authorities: []string{oap.Address()}, }, rmPrivKey, deadline); err != nil { log.Fatalf("Failed to create reward pool: %v", err) } diff --git a/pkg/api/core/v1/types.pb.go b/pkg/api/core/v1/types.pb.go index cbf92598..09034063 100644 --- a/pkg/api/core/v1/types.pb.go +++ b/pkg/api/core/v1/types.pb.go @@ -4582,9 +4582,10 @@ type CreateRewardPool struct { // The pool is identified by its Solana reward manager pubkey (base58, // 32 bytes). Subsequent CreateReward / SetRewardPoolAuthorities messages - // and PR3's sender-attestation gate use this same value to resolve the - // pool. There is no separate "pool address" — the pool's identity IS the - // RM pubkey, so pool↔RM binding cannot be set wrong by construction. + // and the validator's sender-attestation gate use this same value to + // resolve the pool. There is no separate "pool address" — the pool's + // identity IS the RM pubkey, so pool↔RM binding cannot be set wrong + // by construction. RewardsManagerPubkey string `protobuf:"bytes,1,opt,name=rewards_manager_pubkey,json=rewardsManagerPubkey,proto3" json:"rewards_manager_pubkey,omitempty"` // Initial set of eth addresses authorized to attest for rewards in this // pool. The recovered signer must be a member of this list. Each entry diff --git a/pkg/core/config/rewards.go b/pkg/core/config/rewards.go index 44dd12b1..2b6d3ea2 100644 --- a/pkg/core/config/rewards.go +++ b/pkg/core/config/rewards.go @@ -3,42 +3,23 @@ package config import "github.com/OpenAudio/go-openaudio/pkg/rewards" // Solana reward manager pubkeys for the AUDIO mint, per environment. -// -// AUDIO is intentionally outside the reward-pool primitive: pool-managed -// authority sets are for per-launchpad-coin rotation, while AUDIO continues -// to be governed by the network-wide validator/AAO trust set. The values -// here are used as a denylist by validateCreateRewardPool, which refuses -// to create a pool whose rewards_manager_pubkey matches the AUDIO RM — -// otherwise an attacker could create a pool for the AUDIO RM with their -// own keys as initial authorities and have validators sign AUDIO sender -// attestations on their behalf. -// -// Empty values disable the denylist for that environment (no enforcement). -// Sandbox / devnet typically don't have a real AUDIO RM and can be left -// empty; prod is required. -// -// Staging is intentionally left empty: staging doesn't run with a real -// AUDIO mint, and the rotation flow is exercised against test launchpad -// RMs that have first-class pools, so the denylist would never fire for -// any legitimate staging request. Setting a non-empty staging value -// would just be a misconfiguration risk with no upside. +// Used as a denylist by validateCreateRewardPool: pools cannot be +// created for the AUDIO RM, so AUDIO sender attestations stay on the +// network-wide validator/AAO trust set rather than shifting to pool- +// controlled authorities. Empty values disable the denylist for that +// environment. var ( - DevAudioRewardsManagerPubkey = "DJPzVothq58SmkpRb1ATn5ddN2Rpv1j2TcGvM3XsHf1c" - StageAudioRewardsManagerPubkey = "" - ProdAudioRewardsManagerPubkey = "71hWFVYokLaN1PNYzTAWi13EfJ7Xt9VbSWUKsXUT8mxE" + DevAudioRewardsManagerPubkey = "DJPzVothq58SmkpRb1ATn5ddN2Rpv1j2TcGvM3XsHf1c" + ProdAudioRewardsManagerPubkey = "71hWFVYokLaN1PNYzTAWi13EfJ7Xt9VbSWUKsXUT8mxE" ) -// AudioRewardsManagerPubkey returns the configured AUDIO RM pubkey for the -// current runtime environment, or "" if none is configured. Callers MUST -// treat "" as "no denylist enforcement" — they should not refuse to do work -// just because the constant is empty (the validator network was running -// before this denylist existed). +// AudioRewardsManagerPubkey returns the configured AUDIO RM pubkey for +// the current runtime environment, or "" if none is configured. Callers +// MUST treat "" as "no denylist enforcement." func AudioRewardsManagerPubkey() string { switch GetRuntimeEnvironment() { case "prod", "production", "mainnet": return ProdAudioRewardsManagerPubkey - case "stage", "staging", "testnet": - return StageAudioRewardsManagerPubkey case "dev", "development", "devnet", "local", "sandbox": return DevAudioRewardsManagerPubkey default: diff --git a/pkg/core/db/reads.sql.go b/pkg/core/db/reads.sql.go index 99e749f4..b1731fa3 100644 --- a/pkg/core/db/reads.sql.go +++ b/pkg/core/db/reads.sql.go @@ -1493,13 +1493,13 @@ limit 1 // Resolves a launchpad-derived per-mint claim authority (lowercased eth // hex) to the Solana reward manager state account that mint's rewards -// live under. Used by PR2's wire-compat layer at block-sync replay time: -// when finalizeLegacyCreateReward sees an inline claim_authorities array, -// it looks up the RM from any one of its lowercased entries and routes -// the reward into a pool keyed by that RM — matching exactly what PR1's -// backfill produced for pre-migration rows. Returns ErrNoRows if none of -// the requested authorities is in the launchpad mapping (e.g., AUDIO -// rewards or test fixtures). +// live under. Used by the wire-compat layer at block-sync replay time: +// when finalizeLegacyCreateReward sees an inline claim_authorities +// array, it looks up the RM from any one of its lowercased entries and +// routes the reward into a pool keyed by that RM — matching exactly +// what the migration backfill produced for pre-migration rows. +// Returns ErrNoRows if none of the requested authorities is in the +// launchpad mapping (e.g., AUDIO rewards or test fixtures). func (q *Queries) GetLaunchpadRMByAuthority(ctx context.Context, dollar_1 []string) (string, error) { row := q.db.QueryRow(ctx, getLaunchpadRMByAuthority, dollar_1) var rewards_manager_pubkey string diff --git a/pkg/core/db/sql/migrations/00034_reward_pools.sql b/pkg/core/db/sql/migrations/00034_reward_pools.sql index d3f4b671..9fe949cf 100644 --- a/pkg/core/db/sql/migrations/00034_reward_pools.sql +++ b/pkg/core/db/sql/migrations/00034_reward_pools.sql @@ -3,10 +3,10 @@ -- Reward pools group the eth addresses authorized to attest for rewards -- under a specific Solana reward manager. The pool is identified BY the -- reward manager pubkey (32-byte base58) — there is no separate "pool --- address" concept. PR2's CreateRewardPool / SetRewardPoolAuthorities txs --- mutate this table; PR3's sender-attestation gate consults it to decide --- whether to sign add/delete attestations for a given (RM, eth address) --- pair. +-- address" concept. The CreateRewardPool / SetRewardPoolAuthorities +-- cometbft txs mutate this table; the validator's sender-attestation +-- gate consults it to decide whether to sign add/delete attestations +-- for a given (RM, eth address) pair. create table if not exists core_reward_pools ( rewards_manager_pubkey text primary key, authorities text[] not null default '{}', @@ -26,10 +26,10 @@ create index if not exists idx_core_reward_pools_authorities on core_reward_pool -- 1. The backfill block below uses it to find each existing reward row's -- RM (by looking for any one of its claim_authorities that matches a -- known launchpad authority). --- 2. PR2's wire-compat layer (finalizeLegacyCreateReward) queries it at --- block-sync replay time to produce the same RM-bound state the --- migration produces, so historical replay and the migration agree --- on the resulting apphash. +-- 2. The wire-compat layer (finalizeLegacyCreateReward) queries it +-- at block-sync replay time to produce the same RM-bound state +-- the migration produces, so historical replay and the migration +-- agree on the resulting apphash. -- -- The table must remain populated and queryable as long as any legacy- -- format CreateReward txs may still be replayed during block sync. Future @@ -115,20 +115,19 @@ on conflict (authority) do nothing; -- Add rewards_manager_pubkey FK to core_rewards. Nullable: rows whose -- claim_authorities don't include any known launchpad-derived authority -- (e.g., test fixtures, abandoned rewards) stay NULL after the backfill --- and have no pool. PR3's sender-attestation gate refuses to operate on --- NULL-RM rewards (no pool, no authority data). +-- and have no pool. The validator's sender-attestation gate refuses to +-- operate on NULL-RM rewards (no pool, no authority data). alter table core_rewards add column if not exists rewards_manager_pubkey text; create index if not exists idx_core_rewards_rewards_manager_pubkey on core_rewards (rewards_manager_pubkey); -- Backfill step 1: insert one core_reward_pools row per (RM, canonical -- authorities) pair seen in core_rewards. The pool's RM is determined by -- finding any one of the row's claim_authorities that matches a launchpad --- authority — so each unique authority set becomes a real RM-bound pool, --- not a synthetic mig_ identifier. The pool's authorities array is --- the canonical (trim, lower, dedup, sort) form of the row's full --- claim_authorities, preserving the partner-signer / leaked-key entries --- as the pool's *initial* authorities. PR2's SetRewardPoolAuthorities is --- the rotation surface that drains them out. +-- authority — so each unique authority set becomes a real RM-bound pool. +-- The pool's authorities array is the canonical (trim, lower, dedup, +-- sort) form of the row's full claim_authorities, preserving any +-- additional entries as the pool's *initial* authorities; rotation via +-- SetRewardPoolAuthorities is the surface that drains them out. -- Per-RM authority union: for each RM, take the UNION of authorities -- across every reward whose claim_authorities contain ANY of that RM's -- launchpad-derived keys. This guards against the case where two @@ -203,7 +202,7 @@ where r.address = mapped.reward_address; -- init events; rows that don't match are stale fixture data, never live -- production rewards. Such rewards are unclaimable post-migration; their -- claim authority recovers by submitting CreateRewardPool + a fresh --- CreateReward via PR2's transactions. +-- CreateReward via the cometbft tx surface. alter table core_rewards add constraint fk_core_rewards_rewards_manager_pubkey diff --git a/pkg/core/db/sql/reads.sql b/pkg/core/db/sql/reads.sql index 0b0e20b5..f7d2ae3e 100644 --- a/pkg/core/db/sql/reads.sql +++ b/pkg/core/db/sql/reads.sql @@ -663,13 +663,13 @@ order by rewards_manager_pubkey; -- name: GetLaunchpadRMByAuthority :one -- Resolves a launchpad-derived per-mint claim authority (lowercased eth -- hex) to the Solana reward manager state account that mint's rewards --- live under. Used by PR2's wire-compat layer at block-sync replay time: --- when finalizeLegacyCreateReward sees an inline claim_authorities array, --- it looks up the RM from any one of its lowercased entries and routes --- the reward into a pool keyed by that RM — matching exactly what PR1's --- backfill produced for pre-migration rows. Returns ErrNoRows if none of --- the requested authorities is in the launchpad mapping (e.g., AUDIO --- rewards or test fixtures). +-- live under. Used by the wire-compat layer at block-sync replay time: +-- when finalizeLegacyCreateReward sees an inline claim_authorities +-- array, it looks up the RM from any one of its lowercased entries and +-- routes the reward into a pool keyed by that RM — matching exactly +-- what the migration backfill produced for pre-migration rows. +-- Returns ErrNoRows if none of the requested authorities is in the +-- launchpad mapping (e.g., AUDIO rewards or test fixtures). select rewards_manager_pubkey from launchpad_authority_rm where authority = any($1::text[]) diff --git a/pkg/core/server/connect.go b/pkg/core/server/connect.go index de822ba4..cf099a63 100644 --- a/pkg/core/server/connect.go +++ b/pkg/core/server/connect.go @@ -1065,11 +1065,11 @@ func (c *CoreService) GetStatus(ctx context.Context, _ *connect.Request[v1.GetSt // // Restored from the temporary kill-switch (#215) now that the rotation // primitive is in place. The authority check uses dbReward.ClaimAuthorities, -// which post-PR1 is sourced from coalesce(p.authorities, '{}') via the -// LEFT JOIN on core_reward_pools — i.e., it reflects the current pool -// membership for the reward's RM, not the stale row-frozen list. So -// rotating an authority out via SetRewardPoolAuthorities immediately -// revokes their ability to authenticate claim attestations. +// which is sourced from coalesce(p.authorities, '{}') via the LEFT JOIN +// on core_reward_pools — i.e., it reflects the current pool membership +// for the reward's RM, not the stale row-frozen list. So rotating an +// authority out via SetRewardPoolAuthorities immediately revokes their +// ability to authenticate claim attestations. func (c *CoreService) GetRewardAttestation(ctx context.Context, req *connect.Request[v1.GetRewardAttestationRequest]) (*connect.Response[v1.GetRewardAttestationResponse], error) { // Trim user-supplied addresses up front. RewardClaim.Compile // hex-decodes ethRecipientAddress / claimAuthority and surfaces @@ -1120,8 +1120,8 @@ func (c *CoreService) GetRewardAttestation(ctx context.Context, req *connect.Req } // dbReward.ClaimAuthorities is fed by the pool's current authorities - // (post-PR1 LEFT JOIN aliasing). Building a Reward from it gives us - // pool-gated authentication for free. + // via the LEFT JOIN aliasing in GetReward. Building a Reward from it + // gives us pool-gated authentication for free. claimAuthorities := make([]rewards.ClaimAuthority, len(dbReward.ClaimAuthorities)) for i, ca := range dbReward.ClaimAuthorities { claimAuthorities[i] = rewards.ClaimAuthority{Address: ca} @@ -1264,10 +1264,11 @@ func (c *CoreService) GetReward(ctx context.Context, req *connect.Request[v1.Get } // GetRewardPool returns a reward pool keyed by its Solana reward manager -// pubkey. Every pool in core_reward_pools is RM-bound (PR1's backfill -// resolves each existing reward to a real RM via the launchpad mapping; -// CreateRewardPool validates that new pools use a base58 32-byte pubkey). -// There is no separate synthetic-pool surface to filter out. +// pubkey. Every pool in core_reward_pools is RM-bound (the backfill +// resolves each pre-existing reward to a real RM via the launchpad +// mapping; CreateRewardPool validates that new pools use a base58 +// 32-byte pubkey). There is no separate synthetic-pool surface to +// filter out. func (c *CoreService) GetRewardPool(ctx context.Context, req *connect.Request[v1.GetRewardPoolRequest]) (*connect.Response[v1.GetRewardPoolResponse], error) { // Normalize and shape-validate up front so malformed input returns // InvalidArgument deterministically instead of falling through to a @@ -1879,7 +1880,7 @@ func (c *CoreService) GetRewardSenderAttestation(ctx context.Context, req *conne } if isPoolGated { - if !contains(pool.Authorities, address) { + if !slices.Contains(pool.Authorities, strings.ToLower(address)) { return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("address not in pool.authorities for RM %s", rewardsManagerPubkey)) } } else { @@ -1943,7 +1944,7 @@ func (c *CoreService) GetDeleteRewardSenderAttestation(ctx context.Context, req } if isPoolGated { - if contains(pool.Authorities, address) { + if slices.Contains(pool.Authorities, strings.ToLower(address)) { return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("address is still a current authority of the pool for RM %s; rotate it out first via SetRewardPoolAuthorities", rewardsManagerPubkey)) } } else { diff --git a/pkg/core/server/reward_pools.go b/pkg/core/server/reward_pools.go index 88d4294d..cab76291 100644 --- a/pkg/core/server/reward_pools.go +++ b/pkg/core/server/reward_pools.go @@ -5,6 +5,7 @@ import ( "crypto/ed25519" "errors" "fmt" + "slices" "strings" corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" @@ -128,7 +129,7 @@ func (s *Server) validateCreateRewardPool(ctx context.Context, envelope *corev1. return err } canonical := rewards.CanonicalAuthorities(msg.Authorities) - if !contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { + if !slices.Contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s not in initial authorities", ErrRewardUnauthorized, signer) } if _, err := s.db.GetRewardPool(ctx, msg.RewardsManagerPubkey); err == nil { @@ -185,15 +186,15 @@ func validateRewardsManagerPubkeyShape(pubkey string) error { // CreateReward. Allowing a pool to be created for the AUDIO RM would // shift AUDIO sender attestations to pool-controlled authorities, // defeating the AUDIO trust model — AUDIO is intentionally governed by -// the network-wide validator/AAO trust set in PR3's sender-attestation -// gate (the gate falls back to validator/AAO for any RM that has no -// pool). +// the network-wide validator/AAO trust set in senderGateForRM (the +// gate falls back to validator/AAO when the configured AUDIO RM has +// no pool). // -// First-class pools must use a real RM pubkey because PR3's -// sender-attestation gate uses the same value to bind the pool↔RM. PR1's -// backfill resolves each existing reward row to a real RM via the -// launchpad_authority_rm mapping, so there are no synthetic-pool -// identifiers in production state to special-case here. +// First-class pools must use a real RM pubkey because senderGateForRM +// uses the same value to bind the pool↔RM. The backfill resolves each +// existing reward row to a real RM via the launchpad_authority_rm +// mapping, so there are no synthetic-pool identifiers in production +// state to special-case here. func validateRewardsManagerPubkey(pubkey string) error { if err := validateRewardsManagerPubkeyShape(pubkey); err != nil { return err @@ -283,7 +284,7 @@ func (s *Server) finalizeCreateRewardPool(ctx context.Context, envelope *corev1. return err } canonical := rewards.CanonicalAuthorities(msg.Authorities) - if !contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { + if !slices.Contains(canonical, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s not in initial authorities", ErrRewardUnauthorized, signer) } return s.getDb().InsertRewardPool(ctx, db.InsertRewardPoolParams{ @@ -331,17 +332,9 @@ func (s *Server) checkPoolAuthorization(ctx context.Context, q *db.Queries, rewa } return fmt.Errorf("failed to load pool %s: %w", rewardsManagerPubkey, err) } - if !contains(pool.Authorities, strings.ToLower(strings.TrimSpace(signer))) { + if !slices.Contains(pool.Authorities, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s not authorized for pool %s", ErrRewardUnauthorized, signer, rewardsManagerPubkey) } return nil } -func contains(haystack []string, needle string) bool { - for _, h := range haystack { - if strings.EqualFold(h, needle) { - return true - } - } - return false -} diff --git a/pkg/core/server/reward_pools_test.go b/pkg/core/server/reward_pools_test.go index c9e40e8e..510faff5 100644 --- a/pkg/core/server/reward_pools_test.go +++ b/pkg/core/server/reward_pools_test.go @@ -63,17 +63,14 @@ func TestValidateRewardsManagerPubkey_AudioDenylist(t *testing.T) { // Save and restore so the test is hermetic. prevDev := config.DevAudioRewardsManagerPubkey - prevStage := config.StageAudioRewardsManagerPubkey prevProd := config.ProdAudioRewardsManagerPubkey defer func() { config.DevAudioRewardsManagerPubkey = prevDev - config.StageAudioRewardsManagerPubkey = prevStage config.ProdAudioRewardsManagerPubkey = prevProd }() - // Set all three so the test passes regardless of which env happens to be + // Set both so the test passes regardless of which env happens to be // resolved (default is "prod" per GetRuntimeEnvironment). config.DevAudioRewardsManagerPubkey = audioRM - config.StageAudioRewardsManagerPubkey = audioRM config.ProdAudioRewardsManagerPubkey = audioRM if err := validateRewardsManagerPubkey(audioRM); err == nil { diff --git a/pkg/core/server/rewards.go b/pkg/core/server/rewards.go index 46c4a3ef..f7e4a764 100644 --- a/pkg/core/server/rewards.go +++ b/pkg/core/server/rewards.go @@ -5,7 +5,9 @@ import ( "errors" "fmt" "net/http" + "slices" "strconv" + "strings" corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" @@ -139,8 +141,8 @@ func (s *Server) validateCreateReward(ctx context.Context, createReward *corev1. // by the Solana reward manager pubkey, and only pool members can issue // rewards under that RM. Without this binding, a member of one pool // could issue rewards under any other pool's RM and inherit its - // attestation rights — which is exactly what PR3's per-RM gate is - // designed to prevent. + // attestation rights — which is exactly what the per-RM + // sender-attestation gate is designed to prevent. // // The legacy permissionless path (inline claim_authorities, no RM) is // preserved only for historical replay via the wire-compat layer; new @@ -159,7 +161,7 @@ func (s *Server) validateDeleteReward(ctx context.Context, deleteReward *corev1. } return fmt.Errorf("failed to get existing reward for validation: %w", err) } - if !contains(existingReward.ClaimAuthorities, signer) { + if !slices.Contains(existingReward.ClaimAuthorities, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s not authorized to delete reward %s", ErrRewardUnauthorized, signer, deleteReward.Address) } return nil @@ -280,7 +282,7 @@ func (s *Server) finalizeDeleteReward(ctx context.Context, deleteReward *corev1. } return fmt.Errorf("failed to get reward at finalize: %w", err) } - if !contains(existingReward.ClaimAuthorities, signer) { + if !slices.Contains(existingReward.ClaimAuthorities, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: signer %s no longer authorized to delete reward %s", ErrRewardUnauthorized, signer, deleteReward.Address) } if err := qtx.DeleteCoreReward(ctx, deleteReward.Address); err != nil { diff --git a/pkg/core/server/rewards_legacy.go b/pkg/core/server/rewards_legacy.go index e7b7f723..a89c8e88 100644 --- a/pkg/core/server/rewards_legacy.go +++ b/pkg/core/server/rewards_legacy.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "slices" + "strings" corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" "github.com/OpenAudio/go-openaudio/pkg/common" @@ -18,9 +20,9 @@ import ( // Legacy reward wire-compat layer. // // Pre-pool-rollout, RewardMessage was a oneof of CreateReward / DeleteReward -// with deadline + signature embedded inside each action. PR #225 introduced -// the body+signature envelope, which is wire-incompatible: legacy bytes have -// no field at tag 1 (the new body) so the new proto unmarshals them with +// with deadline + signature embedded inside each action. The current +// body+signature envelope is wire-incompatible: legacy bytes have no +// field at tag 1 (the new body) so the new proto unmarshals them with // Body == nil. // // This file handles the case where a legacy-shaped reward tx is encountered @@ -67,11 +69,12 @@ func tryParseLegacyReward(rm *corev1.RewardMessage) (*corev1.LegacyRewardMessage // finalizeLegacyRewardTransaction applies a legacy-shape reward tx. For // CreateReward, the reward's RM is resolved by looking up any one of its -// inline claim_authorities in the launchpad_authority_rm mapping (PR1's -// migration populates this table); rewards whose authorities don't match -// any known launchpad authority are inserted with NULL rewards_manager_pubkey. -// For DeleteReward, the row is removed after re-checking authorization -// against the existing reward's pool authorities. +// inline claim_authorities in the launchpad_authority_rm mapping (the +// schema migration populates this table); rewards whose authorities +// don't match any known launchpad authority are inserted with NULL +// rewards_manager_pubkey. For DeleteReward, the row is removed after +// re-checking authorization against the existing reward's pool +// authorities. func (s *Server) finalizeLegacyRewardTransaction(ctx context.Context, req *abcitypes.FinalizeBlockRequest, legacy *corev1.LegacyRewardMessage, txhash string, messageIndex int64) error { switch a := legacy.Action.(type) { case *corev1.LegacyRewardMessage_Create: @@ -130,11 +133,12 @@ func (s *Server) finalizeLegacyCreateReward(ctx context.Context, req *abcitypes. // Resolve the reward's RM by looking for a launchpad-derived authority // among the message's inline claim_authorities. The launchpad_authority_rm - // table (populated in PR1) maps each per-mint claim authority (lowercased - // eth hex) to the Solana reward manager state account that mint's rewards - // live under. This produces the SAME (RM, authorities) state PR1's - // backfill produced for pre-migration rows, so historical replay and the - // migration agree on the resulting apphash. + // table (populated by the schema migration) maps each per-mint claim + // authority (lowercased eth hex) to the Solana reward manager state + // account that mint's rewards live under. This produces the SAME + // (RM, authorities) state the migration backfill produced for + // pre-migration rows, so historical replay and the migration agree + // on the resulting apphash. authorityAddrs := make([]string, 0, len(cr.ClaimAuthorities)) for _, auth := range cr.ClaimAuthorities { authorityAddrs = append(authorityAddrs, auth.Address) @@ -196,7 +200,7 @@ func (s *Server) finalizeLegacyDeleteReward(ctx context.Context, dr *corev1.Lega } return fmt.Errorf("legacy delete: failed to get reward: %w", err) } - if !contains(existingReward.ClaimAuthorities, signer) { + if !slices.Contains(existingReward.ClaimAuthorities, strings.ToLower(strings.TrimSpace(signer))) { return fmt.Errorf("%w: legacy signer %s no longer authorized to delete reward %s", ErrRewardUnauthorized, signer, dr.Address) } if err := qtx.DeleteCoreReward(ctx, dr.Address); err != nil { diff --git a/pkg/integration_tests/13_reward_pools_test.go b/pkg/integration_tests/13_reward_pools_test.go index 9531ad5e..5cc93b43 100644 --- a/pkg/integration_tests/13_reward_pools_test.go +++ b/pkg/integration_tests/13_reward_pools_test.go @@ -272,7 +272,7 @@ func TestRewardPoolsLifecycle(t *testing.T) { } }) - // === PR3 sender attestation flow === + // === Sender attestation flow === // // Pool at rmPubkey now has authorities = [alice]; bob has been rotated out. // Validator should: sign create for alice (current authority), refuse diff --git a/proto/core/v1/types.proto b/proto/core/v1/types.proto index a0a21178..474d17fc 100644 --- a/proto/core/v1/types.proto +++ b/proto/core/v1/types.proto @@ -622,9 +622,10 @@ message RewardPoolBody { message CreateRewardPool { // The pool is identified by its Solana reward manager pubkey (base58, // 32 bytes). Subsequent CreateReward / SetRewardPoolAuthorities messages - // and PR3's sender-attestation gate use this same value to resolve the - // pool. There is no separate "pool address" — the pool's identity IS the - // RM pubkey, so pool↔RM binding cannot be set wrong by construction. + // and the validator's sender-attestation gate use this same value to + // resolve the pool. There is no separate "pool address" — the pool's + // identity IS the RM pubkey, so pool↔RM binding cannot be set wrong + // by construction. string rewards_manager_pubkey = 1; // Initial set of eth addresses authorized to attest for rewards in this // pool. The recovered signer must be a member of this list. Each entry