From 466904a452f4170cb025787ea6d413aa7fdf7022 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Thu, 22 May 2025 13:29:54 +0200 Subject: [PATCH 1/2] feat(lazer/publisher_sdk): add wormhole governance source proto --- lazer/Cargo.lock | 2 +- .../proto/governance_instruction.proto | 32 +++++++++++-------- .../proto/pyth_lazer_transaction.proto | 32 ++++++++++++++++++- lazer/publisher_sdk/rust/Cargo.toml | 2 +- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/lazer/Cargo.lock b/lazer/Cargo.lock index 319447ca97..e0da52bb3e 100644 --- a/lazer/Cargo.lock +++ b/lazer/Cargo.lock @@ -3882,7 +3882,7 @@ dependencies = [ [[package]] name = "pyth-lazer-publisher-sdk" -version = "0.1.3" +version = "0.1.4" dependencies = [ "anyhow", "fs-err", diff --git a/lazer/publisher_sdk/proto/governance_instruction.proto b/lazer/publisher_sdk/proto/governance_instruction.proto index d03e56f475..6a37f88339 100644 --- a/lazer/publisher_sdk/proto/governance_instruction.proto +++ b/lazer/publisher_sdk/proto/governance_instruction.proto @@ -12,10 +12,9 @@ import "dynamic_value.proto"; package pyth_lazer_transaction; // Representation of a complete governance instruction. This value will be signed -// by a governance source. +// by a governance source. If the governance source is Wormhole emitter, the VAA payload will +// be the encoded `GovernanceInstruction` message. message GovernanceInstruction { - // [required] Governance source that signed this instruction. - optional GovernanceSource source = 1; // Action requested by this instruction. For the instruction to be accepted, all directives // 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 @@ -29,14 +28,15 @@ message GovernanceInstruction { // 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; - // [required] Sequence number of this instruction. It must be greater than 0. - // It must 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] 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; } @@ -155,10 +155,17 @@ message GovernanceSource { optional bytes public_key = 1; } + message WormholeEmitter { + // [required] Wormhole emitter address. + optional bytes address = 1; + // [required] Wormhole emitter chain ID. Restricted to uint16. + optional uint32 chain_id = 2; + } + // [required] oneof source { SingleEd25519 single_ed25519 = 1; - // TODO: wormhole source goes here. + WormholeEmitter wormhole_emitter = 2; } } @@ -347,4 +354,3 @@ message DeactivateFeed { // governance instruction is processed. optional google.protobuf.Timestamp deactivation_timestamp = 1; } - diff --git a/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto b/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto index 39798dcd46..49fee322d2 100644 --- a/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto +++ b/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto @@ -15,7 +15,11 @@ message SignedLazerTransaction { // [required] signature with public key optional SignatureData signature_data = 1; - // [required] lazer transaction encoded as bytes through protobuf + // [required] lazer transaction payload encoded as bytes. + // If the signature data is a Ed25519SignatureData, the payload is the encoded + // LazerTransaction protobuf message. + // If the signature data is a WormholeMultiSigData, the payload is the encoded + // Wormhole VAA body. optional bytes payload = 2; } @@ -27,9 +31,35 @@ message SignatureData { // [required] type of signature, which determines included data needed for verifying oneof data { Ed25519SignatureData ed25519 = 1; + WormholeMultiSigData wormholeMultiSig = 2; }; } +// Wormhole multisig data which is the proto encoding of the VAA +// header taken from the following wire format: +// https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0001_generic_message_passing.md +message WormholeMultiSigData { + // [required] Protocol version of the entire VAA. + optional int32 version = 1; + + // [required] GuardianSetIndex is the index of the guardian set that signed + // this VAA. Signatures are verified against the public keys in the + // guardian set. + optional int32 guardianSetIndex = 2; + + // Signatures contain a list of signatures made by the guardian set. + repeated WormholeGuardianSignature signatures = 3; +} + +// Wormhole multisig signature +message WormholeGuardianSignature { + // [required] Index of the guardian that signed the transaction + optional int32 index = 1; + + // [required] 65 byte eccdsa signature + optional bytes signature = 2; +} + // ED25519 style signature. Should include a single signature and a single public key // Signature will be verified using public key after determining public key is valid message Ed25519SignatureData { diff --git a/lazer/publisher_sdk/rust/Cargo.toml b/lazer/publisher_sdk/rust/Cargo.toml index 665a3c22d0..8ee0a53845 100644 --- a/lazer/publisher_sdk/rust/Cargo.toml +++ b/lazer/publisher_sdk/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-publisher-sdk" -version = "0.1.3" +version = "0.1.4" edition = "2021" description = "Pyth Lazer Publisher SDK types." license = "Apache-2.0" From f4930f52270598aea78b9c4a69d787df68ada1c3 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Fri, 23 May 2025 11:59:00 +0200 Subject: [PATCH 2/2] fix: address review comments --- .../proto/governance_instruction.proto | 17 ++++++++++++----- .../proto/pyth_lazer_transaction.proto | 9 ++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lazer/publisher_sdk/proto/governance_instruction.proto b/lazer/publisher_sdk/proto/governance_instruction.proto index 6a37f88339..9f655a32e6 100644 --- a/lazer/publisher_sdk/proto/governance_instruction.proto +++ b/lazer/publisher_sdk/proto/governance_instruction.proto @@ -12,8 +12,15 @@ import "dynamic_value.proto"; package pyth_lazer_transaction; // Representation of a complete governance instruction. This value will be signed -// by a governance source. If the governance source is Wormhole emitter, the VAA payload will -// be the encoded `GovernanceInstruction` message. +// 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: +// +// 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 directives // must be successfully applied. In case of any failure, the whole instruction is reverted. @@ -297,8 +304,8 @@ message SetPublisherActive { // Feed is inactive when added, meaning that it will be available to publishers but not to consumers. message AddFeed { - // [required] ID of the price feed. Must be unique (within the shard). - optional uint32 price_feed_id = 1; + // [required] ID of the feed. Must be unique (within the shard). + optional uint32 feed_id = 1; // [required] Feed metadata. Some properties are required (name, exponent, etc.). // Known properties must have the expected type. // Additional arbitrary properties are allowed. @@ -310,7 +317,7 @@ message AddFeed { message UpdateFeed { // [required] ID of the feed that is being updated. Rejects if there is no such feed. - optional uint32 price_feed_id = 1; + optional uint32 feed_id = 1; // [required] // Note: when adding a new variant here, update `Permissions` as well. oneof action { diff --git a/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto b/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto index 49fee322d2..f755c4b7a6 100644 --- a/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto +++ b/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto @@ -16,10 +16,17 @@ message SignedLazerTransaction { optional SignatureData signature_data = 1; // [required] lazer transaction payload encoded as bytes. + // // If the signature data is a Ed25519SignatureData, the payload is the encoded // LazerTransaction protobuf message. + // // If the signature data is a WormholeMultiSigData, the payload is the encoded - // Wormhole VAA body. + // Wormhole VAA body. The Wormhole VAA can be any of the following: + // 1. A governance message from Pyth that updates Lazer state (e.g. a new feed) which + // is an ecoded GovernancePayload according to xc-admin spec which contains the + // encoded GovernanceInstruction protobuf message. + // 2. A governance message from Wormhole that updates Wormhole guardian set which follows + // the Wormhole specification. optional bytes payload = 2; }