syntax = "proto3";

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/empty.proto";

import "dynamic_value.proto";
import "state.proto";

// If any field documented as `[required]` is not present in the instruction,
// the instruction will be rejected.

package pyth_lazer;

// Representation of a complete governance instruction. This value will be signed
// by a governance source.
//
// If the governance source is SingleEd25519, this message will be the payload of LazerTransaction.
//
// If the governance source is Wormhole emitter, this message will be the body of the GovernancePayload which
// is the VAA message Pyth governance sends to Wormhole. The GovernancePayload follows xc-admin spec
// and looks like so:
// <magic:u32><module:u8><action:u8><chain:u16><GovernanceInstruction:bytes>
// You can find the xc-admin spec in: ../../../governance/xc_admin/packages/xc_admin_common/src/governance_payload
message GovernanceInstruction {
    // Action requested by this instruction. For the instruction to be accepted, all items
    // must be successfully applied. In case of any failure, the whole instruction is reverted.
    // However, note that if the instruction targets multiple (or all) shards, each shard will
    // accept or reject the instruction independently of other shards.
    repeated GovernanceInstructionItem items = 2;
    // [optional] If specified, the instruction will be rejected if the current timestamp
    // is less than the specified value. In case of rejection, the same instruction can be resubmitted
    // and executed later once the time requirement is met.
    optional google.protobuf.Timestamp min_execution_timestamp = 3;
    // [optional] If specified, the instruction will be rejected if the current timestamp
    // is greater than the specified value. After `max_execution_timestamp` is in the past,
    // it will no longer be possible to execute this instruction.
    optional google.protobuf.Timestamp max_execution_timestamp = 4;
    // [optional] Sequence number of this instruction. Required for SingleEd25519 governance source
    // and optional for WomrholeEmitter governance source (because Wormhole has its own sequence
    // numbers). If set, it must be greater than 0, and always be increasing, but not required to be
    // strictly sequential (i.e. gaps are allowed). Each shard separately keeps track of the last
    // executed governance instruction and will reject instructions with the same or smaller
    // sequence no. Note that if instructions are received out of order, some of them may become
    // permanently rejected (e.g. if instruction #3 has been successfully processed before
    // instruction #2 was observed, #2 will always be rejected). Sequence numbers are assigned and
    // tracked separately for each governance source.
    optional uint32 governance_sequence_no = 5;
}

// Specifies which shards the governance instruction applies to.
message ShardFilter {
    // The instruction applies to the specified shards.
    message ShardNames {
        // Must not be empty.
        repeated string shard_names = 1;
    }
    // The instruction applies to the specified shard groups.
    message ShardGroups {
        // Must not be empty.
        repeated string shard_groups = 1;
    }
    // [required]
    oneof filter {
        // The instruction applies to all shards.
        google.protobuf.Empty all_shards = 1;
        ShardNames shard_names = 2;
        ShardGroups shard_groups = 3;
    }
}

// An item of a governance instruction.
message GovernanceInstructionItem {
    // [required] Specifies which shards the governance instruction applies to.
    // The instruction applies to each shard independently of other shards and may apply
    // at a different time. The instruction may succeed on some shards and fail on other shards.
    // Note that each shard has its own list of governance sources and permissions,
    // and a `GovernanceInstruction` is issued by a single source, so multiple instructions
    // from different sources may be needed to apply a change to multiple shards or shard groups.
    optional ShardFilter shard_filter = 1;
    // [required]
    // Note: when adding a new variant here, update `Permissions` as well.
    oneof action {
        CreateShard create_shard = 101;
        AddGovernanceSource add_governance_source = 102;
        UpdateGovernanceSource update_governance_source = 103;
        SetShardName set_shard_name = 105;
        SetShardGroup set_shard_group = 106;
        ResetLastSequenceNo reset_last_sequence_no = 107;
        AddPublisher add_publisher = 108;
        UpdatePublisher update_publisher = 109;
        RemovePublisher remove_publisher = 110;
        AddFeed add_feed = 111;
        UpdateFeed update_feed = 112;
        RemoveFeed remove_feed = 113;
    }
}

// Create a new shard. A shard is a partially isolated part of Lazer that has its own state and
// cannot be directly influenced by other shards. The main purpose of shards in Lazer is
// to allow horizontal scaling when the number of feeds grows. Feeds can be divided into subsets
// and each subset will be assigned to a shard.
//
// Shard name will be determined by the value of `GovernanceInstructionItem.filter`.
// This action will be rejected unless `GovernanceInstructionItem.filter` specified a single shard.
// Shard name must be unique across all shards in all groups.
// (Warning: it's not possible to enforce this rule within a shard!)
message CreateShard {
    // [required] ID of the new shard.
    // Shard ID must be unique across all shards in all groups.
    // (Warning: it's not possible to enforce this rule within a shard!)
    optional uint32 shard_id = 1;
    // [required] Group name, e.g. "production", "staging", "testing", etc.
    // Data from shards belonging to the same group can be joined and served to consumers as a whole.
    // Active feed names must be unique within a group, but not across all groups.
    optional string shard_group = 2;
    // [required] Minimal aggregation rate allowed in this shard.
    optional google.protobuf.Duration min_rate = 3;
}

message AddGovernanceSource {
    // [required] Governance source that should be added.
    optional GovernanceSource new_source = 1;
    // [required] Permissions granted to this source.
    optional Permissions permissions = 2;
}

message UpdateGovernanceSource {
    // [required] Governance source that should be updated. Rejects if there is no such source.
    // Rejects if the specified source is the same as the source of the current instruction.
    optional GovernanceSource source = 1;
    // [required]
    // Note: when adding a new variant here, update `Permissions` as well.
    oneof action {
        SetGovernanceSourcePermissions set_governance_source_permissions = 101;
    }
}

message SetGovernanceSourcePermissions {
    // [required] Permissions granted to this source. Replaces all previous permissions.
    optional Permissions permissions = 1;
}

// Set shard name. This action will be rejected if `GovernanceInstructionItem.shard_names` is empty or contains
// more than one item.
message SetShardName {
    // [required] New shard name. Must be unique across all shards in all groups.
    // (Warning: it's not possible to enforce this rule within a shard!)
    optional string shard_name = 1;
}

// Set shard group. This action will be rejected if `GovernanceInstructionItem.shard_names` is empty or contains
// more than one item.
message SetShardGroup {
    // [required] Group name, e.g. "production", "staging", "testing", etc.
    // Data from shards belonging to the same group can be joined and served to consumers as a whole.
    // Active feed names must be unique within a group, but not across all groups.
    optional string shard_group = 1;
}

// Set `last_sequence_no`. This can be used as a workaround in case some updates are lost and
// the services are unable to proceed.
message ResetLastSequenceNo {
    optional uint64 last_sequence_no = 1;
}

message AddPublisher {
    // [required] Publisher ID. Restricted to uint16. Must be different from existing ids.
    optional uint32 publisher_id = 1;
    // [required] Publisher name (only for debug/monitoring/management purposes).
    // Must be different from existing publisher names.
    optional string name = 2;
    // Public keys used to sign publisher update transactions.
    repeated bytes public_keys = 3;
    // [required] If true, the publisher is active, i.e. it's allowed to publish updates.
    optional bool is_active = 4;
}

message UpdatePublisher {
    // [required] ID of the publisher that is being updated. Rejects if there is no such publisher.
    optional uint32 publisher_id = 1;
    // [required]
    // Note: when adding a new variant here, update `Permissions` as well.
    oneof action {
        SetPublisherName set_publisher_name = 101;
        AddPublisherPublicKeys add_publisher_public_keys = 102;
        RemovePublisherPublicKeys remove_publisher_public_keys = 103;
        SetPublisherPublicKeys set_publisher_public_keys = 104;
        SetPublisherActive set_publisher_active = 105;
    }
}

message RemovePublisher {
    // [required] ID of the publisher that is being deleted. Rejects if there is no such publisher.
    optional uint32 publisher_id = 1;
}

message SetPublisherName {
    // [required] New name.
    optional string name = 1;
}

// Add new keys.
message AddPublisherPublicKeys {
    // Must not be empty.
    repeated bytes public_keys = 1;
}

// Remove existing keys.
message RemovePublisherPublicKeys {
    // Must not be empty.
    repeated bytes public_keys = 1;
}

// Remove all existing public keys and add new keys (if specified).
message SetPublisherPublicKeys {
    repeated bytes public_keys = 1;
}

message SetPublisherActive {
    // [required]
    optional bool is_active = 1;
}

// Add a new feed. Refer to `Feed` message fields documentation.
message AddFeed {
    // [required]
    optional uint32 feed_id = 1;
    // [required]
    optional DynamicValue.Map metadata = 3;
    // [required]
    optional string name = 101;
    // [required]
    optional sint32 exponent = 102;
    // [required]
    optional uint32 min_publishers = 103;
    // [required]
    optional google.protobuf.Duration min_rate = 104;
    // [required]
    optional google.protobuf.Duration expiry_time = 105;
    // [required]
    optional string market_schedule = 106;
    // [required]
    optional FeedState state = 107;
    // [required]
    optional FeedKind kind = 108;
    // [required]
    optional bool is_enabled_in_shard = 201;

    // TODO: IDs of publishers enabled for this feed.
    // repeated uint32 permissioned_publishers = 3;
}

message UpdateFeed {
    // [required] ID of the feed that is being updated. Rejects if there is no such feed.
    optional uint32 feed_id = 1;
    // [required]
    // Note: when adding a new variant here, update `Permissions` as well.
    oneof action {
        UpdateFeedProperties update_feed_properties = 101;
        UpdateFeedMetadata update_feed_metadata = 102;
        EnableFeedInShard enable_feed_in_shard = 103;
        DisableFeedInShard disable_feed_in_shard = 104;
    }
}

message RemoveFeed {
    // [required] ID of the feed that is being removed. Rejects if there is no such feed.
    optional uint32 feed_id = 1;
}

// Update a feed's properties. The feed will be updated with values present in each field.
// If a value is not supplied, the corresponding property will remain unchanged.
// Refer to `Feed` message fields documentation.
message UpdateFeedProperties {
    // [optional]
    optional DynamicValue.Map metadata = 3;
    // [optional]
    optional string name = 101;
    // [optional]
    optional sint32 exponent = 102;
    // [optional]
    optional uint32 min_publishers = 103;
    // [optional]
    optional google.protobuf.Duration min_rate = 104;
    // [optional]
    optional google.protobuf.Duration expiry_time = 105;
    // [optional]
    optional string market_schedule = 106;
    // [optional]
    optional FeedState state = 107;
    // [optional]
    optional bool is_enabled_in_shard = 201;
}

message UpdateFeedMetadata {
    // [required] Property name.
    optional string name = 1;
    // [optional] Property value. If unset, the property will be removed.
    optional DynamicValue value = 2;
}

// Set the feed as enabled in this shard or shedule it for a certain timestamp.
// If there was already a pending status change, it will be cleared
// when this governance instruction is processed.
// Warning: there must never be two feeds with the same name enabled at the same time
// within a shard group. This cannot be enforced within a shard. When a feed needs to be
// moved between shards, use `enable_in_shard_timestamp` and `disable_in_shard_timestamp`
// to disable it in the old shard and enable it in the new shard at the same time.
message EnableFeedInShard {
    // [required] The feed will be enabled at the specified timestamp.
    // If `enable_in_shard_timestamp` is already passed,
    // the feed will be enabled immediately when this
    // governance instruction is processed.
    optional google.protobuf.Timestamp enable_in_shard_timestamp = 1;
}

// Set the feed as disabled in this shard or shedule it for a certain timestamp.
// If there was already a pending status change, it will be cleared
// when this governance instruction is processed.
// See also: `EnableFeedInShard` docs.
message DisableFeedInShard {
    // [required] The feed will be disabled at the specified timestamp.
    // If `disable_in_shard_timestamp` is already passed,
    // the feed will be disabled immediately when this
    // governance instruction is processed.
    optional google.protobuf.Timestamp disable_in_shard_timestamp = 1;
}
